From 388a7b889d9f62aa35fd3a3f5f420b32c22c3cbd Mon Sep 17 00:00:00 2001 From: Ahmed Darrazi Date: Mon, 22 Dec 2025 00:18:58 +0100 Subject: [PATCH] chore: remove duplicate feature 185-settings-catalog-readable --- .../IMPLEMENTATION_STATUS.md | 469 ----------------- .../MANUAL_VERIFICATION_GUIDE.md | 312 ------------ specs/185-settings-catalog-readable/plan.md | 414 --------------- specs/185-settings-catalog-readable/spec.md | 240 --------- specs/185-settings-catalog-readable/tasks.md | 472 ------------------ 5 files changed, 1907 deletions(-) delete mode 100644 specs/185-settings-catalog-readable/IMPLEMENTATION_STATUS.md delete mode 100644 specs/185-settings-catalog-readable/MANUAL_VERIFICATION_GUIDE.md delete mode 100644 specs/185-settings-catalog-readable/plan.md delete mode 100644 specs/185-settings-catalog-readable/spec.md delete mode 100644 specs/185-settings-catalog-readable/tasks.md diff --git a/specs/185-settings-catalog-readable/IMPLEMENTATION_STATUS.md b/specs/185-settings-catalog-readable/IMPLEMENTATION_STATUS.md deleted file mode 100644 index ce52f7d..0000000 --- a/specs/185-settings-catalog-readable/IMPLEMENTATION_STATUS.md +++ /dev/null @@ -1,469 +0,0 @@ -# Feature 185: Implementation Status Report - -## Executive Summary - -**Status**: ✅ **Core Implementation Complete** (Phases 1-5) -**Date**: 2025-12-13 -**Remaining Work**: Testing & Manual Verification (Phases 6-7) - -## Implementation Progress - -### ✅ Completed Phases (1-5) - -#### Phase 1: Database Foundation -- ✅ T001: Migration created and applied successfully (73.61ms) -- ✅ T002: SettingsCatalogDefinition model with helper methods -- **Result**: `settings_catalog_definitions` table exists with GIN index on JSONB - -#### Phase 2: Definition Resolver Service -- ✅ T003-T007: Complete SettingsCatalogDefinitionResolver service -- **Features**: - - 3-tier caching: Memory → Database (30 days) → Graph API - - Batch resolution with `$filter=id in (...)` optimization - - Non-blocking cache warming with error handling - - Graceful fallback with prettified definition IDs -- **File**: `app/Services/Intune/SettingsCatalogDefinitionResolver.php` (267 lines) - -#### Phase 3: Snapshot Enrichment -- ✅ T008-T010: Extended PolicySnapshotService -- **Features**: - - Extracts definition IDs from settings (including nested children) - - Calls warmCache() after settings hydration - - Adds metadata: `definition_count`, `definitions_cached` -- **File**: `app/Services/Intune/PolicySnapshotService.php` (extended) - -#### Phase 4: Normalizer Enhancement -- ✅ T011-T014: Extended PolicyNormalizer -- **Features**: - - `normalizeSettingsCatalogGrouped()` main method - - Value formatting: bool → badges, int → formatted, string → truncated - - Grouping by categoryId with fallback to definition ID segments - - Recursive flattening of nested group settings - - Alphabetical sorting of groups -- **File**: `app/Services/Intune/PolicyNormalizer.php` (extended with 8 new methods) - -#### Phase 5: UI Implementation -- ✅ T015-T022: Complete Settings tab with grouped accordion view -- **Features**: - - Filament Section components for collapsible groups - - First group expanded by default - - Setting rows with labels, formatted values, help text - - Alpine.js copy buttons with clipboard API - - Client-side search filtering - - Empty states and fallback warnings - - Dark mode support -- **Files**: - - `resources/views/filament/infolists/entries/settings-catalog-grouped.blade.php` (~130 lines) - - `app/Filament/Resources/PolicyResource.php` (Settings tab extended) - -### ⏳ Pending Phases (6-7) - -#### Phase 6: Manual Verification (T023-T025) -- [ ] T023: Verify JSON tab still works -- [ ] T024: Verify fallback message for uncached definitions -- [ ] T025: Ensure JSON viewer scoped to Policy View only - -**Estimated Time**: ~15 minutes -**Action Required**: Navigate to `/admin/policies/{id}` for Settings Catalog policy - -#### Phase 7: Testing & Validation (T026-T042) -- [ ] T026-T031: Unit tests (SettingsCatalogDefinitionResolverTest, PolicyNormalizerSettingsCatalogTest) -- [ ] T032-T037: Feature tests (PolicyViewSettingsCatalogReadableTest) -- [ ] T038-T039: Pest suite execution, Pint formatting -- [ ] T040-T042: Git review, migration check, manual QA walkthrough - -**Estimated Time**: ~4-5 hours -**Action Required**: Write comprehensive test coverage - ---- - -## Code Quality Verification - -### ✅ Laravel Pint -- **Status**: PASS - 32 files formatted -- **Command**: `./vendor/bin/sail pint --dirty` -- **Result**: All code compliant with Laravel coding standards - -### ✅ Cache Management -- **Command**: `./vendor/bin/sail artisan optimize:clear` -- **Result**: All caches cleared (config, views, routes, Blade, Filament) - -### ✅ Database Migration -- **Command**: `./vendor/bin/sail artisan migrate` -- **Result**: `settings_catalog_definitions` table exists -- **Verification**: `Schema::hasTable('settings_catalog_definitions')` returns `true` - ---- - -## Architecture Overview - -### Service Layer - -``` -PolicySnapshotService - ↓ (extracts definition IDs) -SettingsCatalogDefinitionResolver - ↓ (resolves definitions) -PolicyNormalizer - ↓ (groups & formats) -PolicyResource (Filament) - ↓ (renders) -settings-catalog-grouped.blade.php -``` - -### Caching Strategy - -``` -Request - ↓ -Memory Cache (Laravel Cache, request-level) - ↓ (miss) -Database Cache (30 days TTL) - ↓ (miss) -Graph API (/deviceManagement/configurationSettings) - ↓ (store) -Database + Memory - ↓ (fallback on Graph failure) -Prettified Definition ID -``` - -### UI Flow - -``` -Policy View (Filament) - ↓ -Tabs: Settings | JSON - ↓ (Settings tab) -Check metadata.definitions_cached - ↓ (true) -settings_grouped ViewEntry - ↓ -normalizeSettingsCatalogGrouped() - ↓ -Blade Component - ↓ -Accordion Groups (Filament Sections) - ↓ -Setting Rows (label, value, help text, copy button) -``` - ---- - -## Files Created/Modified - -### Created Files (5) - -1. **database/migrations/2025_12_13_212126_create_settings_catalog_definitions_table.php** - - Purpose: Cache setting definitions from Graph API - - Schema: 9 columns + timestamps, GIN index on JSONB - - Status: ✅ Applied (73.61ms) - -2. **app/Models/SettingsCatalogDefinition.php** - - Purpose: Eloquent model for cached definitions - - Methods: `findByDefinitionId()`, `findByDefinitionIds()` - - Status: ✅ Complete - -3. **app/Services/Intune/SettingsCatalogDefinitionResolver.php** - - Purpose: Fetch and cache definitions with 3-tier strategy - - Lines: 267 - - Methods: `resolve()`, `resolveOne()`, `warmCache()`, `clearCache()`, `prettifyDefinitionId()` - - Status: ✅ Complete with error handling - -4. **resources/views/filament/infolists/entries/settings-catalog-grouped.blade.php** - - Purpose: Blade template for grouped settings accordion - - Lines: ~130 - - Features: Alpine.js interactivity, Filament Sections, search filtering - - Status: ✅ Complete with dark mode support - -5. **specs/185-settings-catalog-readable/** (Directory with 3 files) - - `spec.md` - Complete feature specification - - `plan.md` - Implementation plan - - `tasks.md` - 42 tasks with FR traceability - - Status: ✅ Complete with implementation notes - -### Modified Files (3) - -1. **app/Services/Intune/PolicySnapshotService.php** - - Changes: Added `SettingsCatalogDefinitionResolver` injection - - New method: `extractDefinitionIds()` (recursive extraction) - - Extended method: `hydrateSettingsCatalog()` (cache warming + metadata) - - Status: ✅ Extended without breaking existing functionality - -2. **app/Services/Intune/PolicyNormalizer.php** - - Changes: Added `SettingsCatalogDefinitionResolver` injection - - New methods: 8 methods (~200 lines) - - `normalizeSettingsCatalogGrouped()` (main entry point) - - `extractAllDefinitionIds()`, `flattenSettingsCatalogForGrouping()` - - `formatSettingsCatalogValue()`, `groupSettingsByCategory()` - - `extractCategoryFromDefinitionId()`, `formatCategoryTitle()` - - Status: ✅ Extended with comprehensive formatting/grouping logic - -3. **app/Filament/Resources/PolicyResource.php** - - Changes: Extended Settings tab in `policy_content` Tabs - - New entries: - - `settings_grouped` ViewEntry (uses Blade component) - - `definitions_not_cached` TextEntry (fallback message) - - Conditional rendering: Grouped view only if `definitions_cached === true` - - Status: ✅ Extended Settings tab, JSON tab preserved - ---- - -## Verification Checklist (Pre-Testing) - -### ✅ Code Quality -- [X] Laravel Pint passed (32 files) -- [X] All code formatted with PSR-12 conventions -- [X] No Pint warnings or errors - -### ✅ Database -- [X] Migration applied successfully -- [X] Table exists with correct schema -- [X] Indexes created (definition_id unique, category_id, GIN on raw) - -### ✅ Service Injection -- [X] SettingsCatalogDefinitionResolver registered in service container -- [X] PolicySnapshotService constructor updated -- [X] PolicyNormalizer constructor updated -- [X] Laravel auto-resolves dependencies - -### ✅ Caching -- [X] All caches cleared (config, views, routes, Blade, Filament) -- [X] Blade component compiled -- [X] Filament schema cache refreshed - -### ✅ UI Integration -- [X] Settings tab extended with grouped view -- [X] JSON tab preserved from Feature 002 -- [X] Conditional rendering based on metadata -- [X] Fallback message implemented - -### ⏳ Manual Verification Pending -- [ ] Navigate to Policy View for Settings Catalog policy -- [ ] Verify accordion renders with groups -- [ ] Verify display names shown (not raw definition IDs) -- [ ] Verify values formatted (badges, numbers, truncated strings) -- [ ] Test search filtering -- [ ] Test copy buttons -- [ ] Switch to JSON tab, verify snapshot renders -- [ ] Test fallback for policy without cached definitions -- [ ] Test dark mode toggle - -### ⏳ Testing Pending -- [ ] Unit tests written and passing -- [ ] Feature tests written and passing -- [ ] Performance benchmarks validated - ---- - -## Next Steps (Priority Order) - -### Immediate (Phase 6 - Manual Verification) - -1. **Open Policy View** (5 min) - - Navigate to `/admin/policies/{id}` for Settings Catalog policy - - Verify page loads without errors - - Check browser console for JavaScript errors - -2. **Verify Tabs & Accordion** (5 min) - - Confirm "Settings" and "JSON" tabs visible - - Click Settings tab, verify accordion renders - - Verify groups collapsible (first expanded by default) - - Click JSON tab, verify snapshot renders with copy button - -3. **Verify Display & Formatting** (5 min) - - Check setting labels show display names (not `device_vendor_msft_...`) - - Verify bool values show as "Enabled"/"Disabled" badges (green/gray) - - Verify int values formatted with separators (e.g., "1,000") - - Verify long strings truncated with "..." and copy button - -4. **Test Search & Fallback** (5 min) - - Type in search box (if visible), verify filtering works - - Test copy buttons (long values) - - Find policy WITHOUT cached definitions - - Verify fallback message: "Definitions not yet cached..." - - Verify JSON tab still accessible - -**Total Estimated Time**: ~20 minutes - -### Short-Term (Phase 7 - Unit Tests) - -1. **Create Unit Tests** (2-3 hours) - - `tests/Unit/SettingsCatalogDefinitionResolverTest.php` - - Test `resolve()` with batch IDs - - Test memory cache hit - - Test database cache hit - - Test Graph API fetch - - Test fallback prettification - - Test non-blocking warmCache() - - `tests/Unit/PolicyNormalizerSettingsCatalogTest.php` - - Test `normalizeSettingsCatalogGrouped()` output structure - - Test value formatting (bool, int, string, choice) - - Test grouping by categoryId - - Test fallback grouping by definition ID segments - - Test recursive definition ID extraction - -2. **Create Feature Tests** (2 hours) - - `tests/Feature/Filament/PolicyViewSettingsCatalogReadableTest.php` - - Test Settings Catalog policy view shows tabs - - Test Settings tab shows display names (not definition IDs) - - Test values formatted correctly (badges, numbers, truncation) - - Test search filters settings - - Test fallback message when definitions not cached - - Test JSON tab still accessible - -3. **Run Test Suite** (15 min) - - `./vendor/bin/sail artisan test --filter=SettingsCatalog` - - Fix any failures - - Verify all tests pass - -**Total Estimated Time**: ~5 hours - -### Medium-Term (Performance & Polish) - -1. **Performance Testing** (1 hour) - - Create test policy with 200+ settings - - Measure render time (target: <2s) - - Measure definition resolution time (target: <500ms for 50 cached) - - Profile with Laravel Telescope or Debugbar - -2. **Manual QA Walkthrough** (1 hour) - - Test all user stories (US-UI-04, US-UI-05, US-UI-06) - - Verify all success criteria (SC-001 to SC-010) - - Test dark mode toggle - - Test with different policy types - - Document any issues or enhancements - -**Total Estimated Time**: ~2 hours - ---- - -## Risk Assessment - -### ✅ Mitigated Risks - -- **Graph API Rate Limiting**: Non-blocking cache warming prevents snapshot save failures -- **Definition Schema Changes**: Raw JSONB storage allows future parsing updates -- **Large Policy Rendering**: Accordion lazy-loading via Filament Sections -- **Missing Definitions**: Multi-layer fallback (prettified IDs → warning badges → info messages) - -### ⚠️ Outstanding Risks - -- **Performance with 500+ Settings**: Not tested yet (Phase 7, T042) -- **Graph API Downtime**: Cache helps, but first sync may fail (acceptable trade-off) -- **Browser Compatibility**: Alpine.js clipboard API requires HTTPS (Dokploy provides SSL) - -### ℹ️ Known Limitations - -- **Search**: Client-side only (Blade-level filtering), no debouncing for large policies -- **Value Expansion**: Long strings truncated, no inline expansion (copy button only) -- **Nested Groups**: Flattened in UI, hierarchy not visually preserved - ---- - -## Constitution Compliance - -### ✅ Safety-First -- Read-only feature, no edit capabilities -- Graceful degradation at every layer -- Non-blocking operations (warmCache) - -### ✅ Immutable Versioning -- Snapshot enrichment adds metadata only -- No modification of existing snapshot data -- Definition cache separate from policy snapshots - -### ✅ Defensive Restore -- Not applicable (read-only feature) - -### ✅ Auditability -- Raw JSON still accessible via JSON tab -- Definition resolution logged via Laravel Log -- Graph API calls auditable via GraphLogger - -### ✅ Tenant-Aware -- Resolver respects tenant scoping via GraphClient -- Definitions scoped per tenant (via Graph API calls) - -### ✅ Graph Abstraction -- Uses existing GraphClientInterface (no direct MS Graph SDK calls) -- Follows existing abstraction patterns - -### ✅ Spec-Driven -- Full spec + plan + tasks before implementation -- FR→Task traceability maintained -- Implementation notes added to tasks.md - ---- - -## Deployment Readiness - -### ✅ Local Development (Laravel Sail) -- [X] Database migration applied -- [X] Services registered in container -- [X] Caches cleared -- [X] Code formatted with Pint -- [X] Table exists with data ready for seeding - -### ⏳ Staging Deployment (Dokploy) -- [ ] Run migrations: `php artisan migrate` -- [ ] Clear caches: `php artisan optimize:clear` -- [ ] Verify environment variables (none required for Feature 185) -- [ ] Test with real Intune tenant data -- [ ] Monitor Graph API rate limits - -### ⏳ Production Deployment (Dokploy) -- [ ] Complete staging validation -- [ ] Feature flag enabled (if applicable) -- [ ] Monitor performance metrics -- [ ] Document rollback plan (drop table, revert code) - ---- - -## Support Information - -### Troubleshooting Guide - -**Issue**: Settings tab shows raw definition IDs instead of display names -- **Cause**: Definitions not cached yet -- **Solution**: Wait for next policy sync (SyncPoliciesJob) or manually trigger sync - -**Issue**: Accordion doesn't render, blank Settings tab -- **Cause**: JavaScript error in Blade component -- **Solution**: Check browser console for errors, verify Alpine.js loaded - -**Issue**: "Definitions not cached" message persists -- **Cause**: Graph API call failed during snapshot -- **Solution**: Check logs for Graph API errors, verify permissions for `/deviceManagement/configurationSettings` endpoint - -**Issue**: Performance slow with large policies -- **Cause**: Too many settings rendered at once -- **Solution**: Consider pagination or virtual scrolling (future enhancement) - -### Maintenance Tasks - -- **Cache Clearing**: Run `php artisan cache:clear` if definitions stale -- **Database Cleanup**: Run `SettingsCatalogDefinition::where('updated_at', '<', now()->subDays(30))->delete()` to prune old definitions -- **Performance Monitoring**: Watch `policy_view` page load times in Telescope - ---- - -## Conclusion - -**Implementation Status**: ✅ **CORE COMPLETE** - -Phases 1-5 implemented successfully with: -- ✅ Database schema + model -- ✅ Definition resolver with 3-tier caching -- ✅ Snapshot enrichment with cache warming -- ✅ Normalizer with grouping/formatting -- ✅ UI with accordion, search, and fallback - -**Next Action**: **Phase 6 Manual Verification** (~20 min) - -Navigate to Policy View and verify all features work as expected before proceeding to Phase 7 testing. - -**Estimated Remaining Work**: ~7 hours -- Phase 6: ~20 min -- Phase 7: ~5-7 hours (tests + QA) - -**Feature Delivery Target**: Ready for staging deployment after Phase 7 completion. diff --git a/specs/185-settings-catalog-readable/MANUAL_VERIFICATION_GUIDE.md b/specs/185-settings-catalog-readable/MANUAL_VERIFICATION_GUIDE.md deleted file mode 100644 index c43dda0..0000000 --- a/specs/185-settings-catalog-readable/MANUAL_VERIFICATION_GUIDE.md +++ /dev/null @@ -1,312 +0,0 @@ -# Feature 185: Manual Verification Guide (Phase 6) - -## Quick Start - -**Estimated Time**: 20 minutes -**Prerequisites**: Settings Catalog policy exists in database with snapshot - ---- - -## Verification Steps - -### Step 1: Navigate to Policy View (2 min) - -1. Open browser: `http://localhost` (or your Sail URL) -2. Login to Filament admin panel -3. Navigate to **Policies** resource -4. Click on a **Settings Catalog** policy (look for `settingsCatalogPolicy` type) - -**Expected Result**: -- ✅ Page loads without errors -- ✅ Policy details visible -- ✅ No browser console errors - -**If it fails**: -- Check browser console for JavaScript errors -- Run `./vendor/bin/sail artisan optimize:clear` -- Verify policy has `versions` relationship loaded - ---- - -### Step 2: Verify Tabs Present (2 min) - -**Action**: Look at the Policy View infolist - -**Expected Result**: -- ✅ "Settings" tab visible -- ✅ "JSON" tab visible -- ✅ Settings tab is default (active) - -**If tabs missing**: -- Check if policy is actually Settings Catalog type -- Verify PolicyResource.php has Tabs component for `policy_content` -- Check Feature 002 JSON viewer implementation - ---- - -### Step 3: Verify Settings Tab - Accordion (5 min) - -**Action**: Click on "Settings" tab (if not already active) - -**Expected Result**: -- ✅ Accordion groups render -- ✅ Each group has: - - Title (e.g., "Device Vendor Msft", "Biometric Authentication") - - Description (if available) - - Setting count badge (e.g., "12 settings") -- ✅ First group expanded by default -- ✅ Other groups collapsed -- ✅ Click group header toggles collapse/expand - -**If accordion missing**: -- Check if `metadata.definitions_cached === true` in snapshot -- Verify normalizer returns groups structure -- Check Blade component exists: `settings-catalog-grouped.blade.php` - ---- - -### Step 4: Verify Display Names (Not Definition IDs) (3 min) - -**Action**: Expand a group and look at setting labels - -**Expected Result**: -- ✅ Labels show human-readable names: - - ✅ "Biometric Authentication" (NOT `device_vendor_msft_policy_biometric_authentication`) - - ✅ "Password Minimum Length" (NOT `device_vendor_msft_policy_password_minlength`) -- ✅ No `device_vendor_msft_...` visible in labels - -**If definition IDs visible**: -- Check if definitions cached in database: `SettingsCatalogDefinition::count()` -- Run policy sync manually to trigger cache warming -- Verify fallback message visible: "Definitions not yet cached..." - ---- - -### Step 5: Verify Value Formatting (5 min) - -**Action**: Look at setting values in different groups - -**Expected Result**: -- ✅ **Boolean values**: Badges with "Enabled" (green) or "Disabled" (gray) -- ✅ **Integer values**: Formatted with separators (e.g., "1,000" not "1000") -- ✅ **String values**: Truncated if >100 chars with "..." -- ✅ **Choice values**: Show choice label (not raw ID) - -**If formatting incorrect**: -- Check `formatSettingsCatalogValue()` method in PolicyNormalizer -- Verify Blade component conditionals for value types -- Inspect browser to see actual rendered HTML - ---- - -### Step 6: Test Copy Buttons (2 min) - -**Action**: Find a setting with a long value, click copy button - -**Expected Result**: -- ✅ Copy button visible for long values -- ✅ Click copy button → clipboard receives value -- ✅ Button shows checkmark for 2 seconds -- ✅ Button returns to copy icon after timeout - -**If copy button missing/broken**: -- Check Alpine.js loaded (inspect page source for `@livewireScripts`) -- Verify clipboard API available (requires HTTPS or localhost) -- Check browser console for JavaScript errors - ---- - -### Step 7: Test Search Filtering (Optional - if search visible) (2 min) - -**Action**: Type in search box (if visible at top of Settings tab) - -**Expected Result**: -- ✅ Search box visible with placeholder "Search settings..." -- ✅ Type search query (e.g., "biometric") -- ✅ Only matching settings shown -- ✅ Non-matching groups hidden/empty -- ✅ Clear search resets view - -**If search not visible**: -- This is expected for MVP (Blade-level implementation, no dedicated input yet) -- Search logic exists in Blade template but may need Livewire wiring - ---- - -### Step 8: Verify JSON Tab (2 min) - -**Action**: Click "JSON" tab - -**Expected Result**: -- ✅ Tab switches to JSON view -- ✅ Snapshot renders with syntax highlighting -- ✅ Copy button visible at top -- ✅ Click copy button → full JSON copied to clipboard -- ✅ Can switch back to Settings tab - -**If JSON tab broken**: -- Verify Feature 002 implementation still intact -- Check `pepperfm/filament-json` package installed -- Verify PolicyResource.php has JSON ViewEntry - ---- - -### Step 9: Test Fallback Message (3 min) - -**Action**: Find a Settings Catalog policy WITHOUT cached definitions (or manually delete definitions from database) - -**Steps to test**: -1. Run: `./vendor/bin/sail artisan tinker` -2. Execute: `\App\Models\SettingsCatalogDefinition::truncate();` -3. Navigate to Policy View for Settings Catalog policy -4. Click Settings tab - -**Expected Result**: -- ✅ Settings tab shows fallback message: - - "Definitions not yet cached. Settings will be shown with raw IDs." - - Helper text: "Switch to JSON tab or wait for next sync" -- ✅ JSON tab still accessible -- ✅ No error messages or broken layout - -**If fallback not visible**: -- Check conditional rendering in PolicyResource.php -- Verify `metadata.definitions_cached` correctly set in snapshot -- Check Blade component has fallback TextEntry - ---- - -### Step 10: Test Dark Mode (Optional) (2 min) - -**Action**: Toggle Filament dark mode (if available) - -**Expected Result**: -- ✅ Accordion groups adjust colors -- ✅ Badges adjust colors (dark mode variants) -- ✅ Text remains readable -- ✅ No layout shifts or broken styles - -**If dark mode broken**: -- Check Blade component uses `dark:` Tailwind classes -- Verify Filament Section components support dark mode -- Inspect browser to see actual computed styles - ---- - -## Success Criteria Checklist - -After completing all steps, mark these off: - -- [ ] **T023**: JSON tab works (from Feature 002) -- [ ] **T024**: Fallback message shows when definitions not cached -- [ ] **T025**: JSON viewer only renders on Policy View (not globally) - ---- - -## Common Issues & Solutions - -### Issue: "Definitions not yet cached" persists - -**Cause**: SyncPoliciesJob hasn't run yet or Graph API call failed - -**Solution**: -1. Manually trigger sync: - ```bash - ./vendor/bin/sail artisan tinker - ``` - ```php - $policy = \App\Models\Policy::first(); - \App\Jobs\SyncPoliciesJob::dispatch(); - ``` -2. Check logs for Graph API errors: - ```bash - ./vendor/bin/sail artisan log:show - ``` - -### Issue: Accordion doesn't render - -**Cause**: Blade component error or missing groups - -**Solution**: -1. Check browser console for errors -2. Verify normalizer output: - ```bash - ./vendor/bin/sail artisan tinker - ``` - ```php - $policy = \App\Models\Policy::first(); - $snapshot = $policy->versions()->orderByDesc('captured_at')->value('snapshot'); - $normalizer = app(\App\Services\Intune\PolicyNormalizer::class); - $groups = $normalizer->normalizeSettingsCatalogGrouped($snapshot['settings'] ?? []); - dd($groups); - ``` - -### Issue: Copy buttons don't work - -**Cause**: Alpine.js not loaded or clipboard API unavailable - -**Solution**: -1. Verify Alpine.js loaded: - - Open browser console - - Type `window.Alpine` → should return object -2. Check HTTPS or localhost (clipboard API requires secure context) -3. Fallback: Use "View JSON" tab and copy from there - ---- - -## Next Steps After Verification - -### If All Tests Pass ✅ - -Proceed to **Phase 7: Testing & Validation** - -1. Write unit tests (T026-T031) -2. Write feature tests (T032-T037) -3. Run Pest suite (T038-T039) -4. Manual QA walkthrough (T040-T042) - -**Estimated Time**: ~5-7 hours - -### If Issues Found ⚠️ - -1. Document issues in `specs/185-settings-catalog-readable/ISSUES.md` -2. Fix critical issues (broken UI, errors) -3. Re-run verification steps -4. Proceed to Phase 7 only after verification passes - ---- - -## Reporting Results - -After completing verification, update tasks.md: - -```bash -# Mark T023-T025 as complete -vim specs/185-settings-catalog-readable/tasks.md -``` - -Add implementation notes: -```markdown -- [X] **T023** Verify JSON tab still works - - **Implementation Note**: Verified tabs functional, JSON viewer renders snapshot - -- [X] **T024** Add fallback for policies without cached definitions - - **Implementation Note**: Fallback message shows info with guidance to JSON tab - -- [X] **T025** Ensure JSON viewer only renders on Policy View - - **Implementation Note**: Verified scoping correct, only shows on Policy resource -``` - ---- - -## Contact & Support - -If verification fails or you need assistance: - -1. Check logs: `./vendor/bin/sail artisan log:show` -2. Review implementation status: `specs/185-settings-catalog-readable/IMPLEMENTATION_STATUS.md` -3. Review code: `app/Services/Intune/`, `app/Filament/Resources/PolicyResource.php` -4. Ask for help with specific error messages and context - ---- - -**End of Manual Verification Guide** diff --git a/specs/185-settings-catalog-readable/plan.md b/specs/185-settings-catalog-readable/plan.md deleted file mode 100644 index e096851..0000000 --- a/specs/185-settings-catalog-readable/plan.md +++ /dev/null @@ -1,414 +0,0 @@ -# Feature 185: Implementation Plan - -## Tech Stack -- **Backend**: Laravel 12, PHP 8.4 -- **Database**: PostgreSQL (JSONB for raw definition storage) -- **Frontend**: Filament 4, Livewire 3, Tailwind CSS -- **Graph Client**: Existing `GraphClientInterface` -- **JSON Viewer**: `pepperfm/filament-json` (installed) - -## Architecture Overview - -### Services Layer -``` -app/Services/Intune/ -├── SettingsCatalogDefinitionResolver.php (NEW) -├── PolicyNormalizer.php (EXTEND) -└── PolicySnapshotService.php (EXTEND) -``` - -### Database Layer -``` -database/migrations/ -└── xxxx_create_settings_catalog_definitions_table.php (NEW) - -app/Models/ -└── SettingsCatalogDefinition.php (NEW) -``` - -### UI Layer -``` -app/Filament/Resources/ -├── PolicyResource.php (EXTEND - infolist with tabs) -└── PolicyVersionResource.php (FUTURE - optional) - -resources/views/filament/infolists/entries/ -└── settings-catalog-grouped.blade.php (NEW - accordion view) -``` - -## Component Responsibilities - -### 1. SettingsCatalogDefinitionResolver -**Purpose**: Fetch and cache setting definitions from Graph API - -**Key Methods**: -- `resolve(array $definitionIds): array` - Batch resolve definitions -- `resolveOne(string $definitionId): ?array` - Single definition lookup -- `warmCache(array $definitionIds): void` - Pre-populate cache -- `clearCache(?string $definitionId = null): void` - Cache invalidation - -**Dependencies**: -- `GraphClientInterface` - Graph API calls -- `SettingsCatalogDefinition` model - Database cache -- Laravel Cache - Memory-level cache - -**Caching Strategy**: -1. Check memory cache (request-level) -2. Check database cache (30-day TTL) -3. Fetch from Graph API -4. Store in DB + memory - -**Graph Endpoints**: -- `/deviceManagement/configurationSettings` (global catalog) -- `/deviceManagement/configurationPolicies/{id}/settings/{settingId}/settingDefinitions` (policy-specific) - -### 2. PolicyNormalizer (Extension) -**Purpose**: Transform Settings Catalog snapshot into UI-ready structure - -**New Method**: `normalizeSettingsCatalog(array $snapshot, array $definitions): array` - -**Output Structure**: -```php -[ - 'type' => 'settings_catalog', - 'groups' => [ - [ - 'title' => 'Windows Hello for Business', - 'description' => 'Configure biometric authentication settings', - 'settings' => [ - [ - 'label' => 'Use biometrics', - 'value_display' => 'Enabled', - 'value_raw' => true, - 'help_text' => 'Allow users to sign in with fingerprint...', - 'definition_id' => 'device_vendor_msft_passportforwork_biometrics_usebiometrics', - 'instance_type' => 'ChoiceSettingInstance' - ] - ] - ] - ] -] -``` - -**Value Formatting Rules**: -- `ChoiceSettingInstance`: Extract choice label from `@odata.type` or value -- `SimpleSetting` (bool): "Enabled" / "Disabled" -- `SimpleSetting` (int): Number formatted with separators -- `SimpleSetting` (string): Truncate >100 chars, add "..." -- `GroupSettingCollectionInstance`: Flatten children recursively - -**Grouping Strategy**: -- Group by `categoryId` from definition metadata -- Fallback: Group by first segment of definition ID (e.g., `device_vendor_msft_`) -- Sort groups alphabetically - -### 3. PolicySnapshotService (Extension) -**Purpose**: Enrich snapshots with definition metadata after hydration - -**Modified Flow**: -``` -1. Hydrate settings from Graph (existing) -2. Extract all settingDefinitionId + children (NEW) -3. Call SettingsCatalogDefinitionResolver::warmCache() (NEW) -4. Add metadata to snapshot: definitions_cached, definition_count (NEW) -5. Save snapshot (existing) -``` - -**Non-Blocking**: Definition resolution should not block policy sync -- Use try/catch for Graph API calls -- Mark `definitions_cached: false` on failure -- Continue with snapshot save - -### 4. PolicyResource (UI Extension) -**Purpose**: Render Settings Catalog policies with readable UI - -**Changes**: -1. Add Tabs component to infolist: - - "Settings" tab (default) - - "JSON" tab (existing Feature 002 implementation) - -2. Settings Tab Structure: - - Search/filter input (top) - - Accordion component (groups) - - Each group: Section with settings table - - Fallback: Show info message if no definitions cached - -3. JSON Tab: - - Existing implementation from Feature 002 - - Shows full snapshot with copy button - -**Conditional Rendering**: -- Show tabs ONLY for `settingsCatalogPolicy` type -- For other policy types: Keep existing simple sections - -## Database Schema - -### Table: `settings_catalog_definitions` -```sql -CREATE TABLE settings_catalog_definitions ( - id BIGSERIAL PRIMARY KEY, - definition_id VARCHAR(500) UNIQUE NOT NULL, - display_name VARCHAR(255) NOT NULL, - description TEXT, - help_text TEXT, - category_id VARCHAR(255), - ux_behavior VARCHAR(100), - raw JSONB NOT NULL, - created_at TIMESTAMP, - updated_at TIMESTAMP -); - -CREATE INDEX idx_definition_id ON settings_catalog_definitions(definition_id); -CREATE INDEX idx_category_id ON settings_catalog_definitions(category_id); -CREATE INDEX idx_raw_gin ON settings_catalog_definitions USING GIN(raw); -``` - -**Indexes**: -- `definition_id` - Primary lookup key -- `category_id` - Grouping queries -- `raw` (GIN) - JSONB queries if needed - -## Graph API Integration - -### Endpoints Used - -1. **Global Catalog** (Preferred): -``` -GET /deviceManagement/configurationSettings -GET /deviceManagement/configurationSettings/{settingDefinitionId} -``` - -2. **Policy-Specific** (Fallback): -``` -GET /deviceManagement/configurationPolicies/{policyId}/settings/{settingId}/settingDefinitions -``` - -### Request Optimization -- Batch requests where possible -- Use `$select` to limit fields -- Use `$filter` for targeted lookups -- Respect rate limits (429 retry logic) - -## UI/UX Flow - -### Policy View Page Flow -1. User navigates to `/admin/policies/{id}` -2. Policy details loaded (existing) -3. Check policy type: - - If `settingsCatalogPolicy`: Show tabs - - Else: Show existing sections -4. Default to "Settings" tab -5. Load normalized settings from PolicyNormalizer -6. Render accordion with groups -7. User can search/filter settings -8. User can switch to "JSON" tab for raw view - -### Settings Tab Layout -``` -┌─────────────────────────────────────────────┐ -│ [Search settings...] [🔍] │ -├─────────────────────────────────────────────┤ -│ ▼ Windows Hello for Business │ -│ ├─ Use biometrics: Enabled │ -│ ├─ Use facial recognition: Disabled │ -│ └─ PIN minimum length: 6 │ -├─────────────────────────────────────────────┤ -│ ▼ Device Lock Settings │ -│ ├─ Password expiration days: 90 │ -│ └─ Password history: 5 │ -└─────────────────────────────────────────────┘ -``` - -### JSON Tab Layout -``` -┌─────────────────────────────────────────────┐ -│ Full Policy Configuration [Copy] │ -├─────────────────────────────────────────────┤ -│ { │ -│ "@odata.type": "...", │ -│ "name": "WHFB Settings", │ -│ "settings": [...] │ -│ } │ -└─────────────────────────────────────────────┘ -``` - -## Error Handling - -### Definition Not Found -- **UI**: Show prettified definition ID -- **Label**: Convert `device_vendor_msft_policy_name` → "Device Vendor Msft Policy Name" -- **Icon**: Info icon with tooltip "Definition not cached" - -### Graph API Failure -- **During Sync**: Mark `definitions_cached: false`, continue -- **During View**: Show cached data or fallback labels -- **Log**: Record Graph API errors for debugging - -### Malformed Snapshot -- **Validation**: Check for required fields before normalization -- **Fallback**: Show raw JSON tab, hide Settings tab -- **Warning**: Display admin-friendly error message - -## Performance Considerations - -### Database Queries -- Eager load definitions for all settings in one query -- Use `whereIn()` for batch lookups -- Index on `definition_id` ensures fast lookups - -### Memory Management -- Request-level cache using Laravel Cache -- Limit batch size to 100 definitions per request -- Clear memory cache after request - -### UI Rendering -- Accordion lazy-loads groups (only render expanded) -- Pagination for policies with >50 groups -- Virtualized list for very large policies (future) - -### Caching TTL -- Database: 30 days (definitions change rarely) -- Memory: Request duration only -- Background refresh: Optional scheduled job - -## Security Considerations - -### Graph API Permissions -- Existing `DeviceManagementConfiguration.Read.All` sufficient -- No new permissions required - -### Data Sanitization -- Escape HTML in display names and descriptions -- Validate definition ID format before lookups -- Prevent XSS in value rendering - -### Audit Logging -- Log definition cache misses -- Log Graph API failures -- Track definition cache updates - -## Testing Strategy - -### Unit Tests -**File**: `tests/Unit/SettingsCatalogDefinitionResolverTest.php` -- Test batch resolution -- Test caching behavior (memory + DB) -- Test fallback when definition not found -- Test Graph API error handling - -**File**: `tests/Unit/PolicyNormalizerSettingsCatalogTest.php` -- Test grouping logic -- Test value formatting (bool, int, choice, string) -- Test fallback labels -- Test nested group flattening - -### Feature Tests -**File**: `tests/Feature/Filament/PolicyViewSettingsCatalogReadableTest.php` -- Mock Graph API responses -- Assert tabs present for Settings Catalog policies -- Assert display names shown (not definition IDs) -- Assert values formatted correctly -- Assert search/filter works -- Assert JSON tab accessible -- Assert graceful degradation for missing definitions - -### Manual QA Checklist -1. Open Policy View for Settings Catalog policy -2. Verify tabs present: "Settings" and "JSON" -3. Verify Settings tab shows groups with accordion -4. Verify display names shown (not raw IDs) -5. Verify values formatted (True/False, numbers, etc.) -6. Test search: Type setting name, verify filtering -7. Switch to JSON tab, verify snapshot shown -8. Test copy button in JSON tab -9. Test dark mode toggle -10. Test with policy missing definitions (fallback labels) - -## Deployment Steps - -### 1. Database Migration -```bash -./vendor/bin/sail artisan migrate -``` - -### 2. Cache Warming (Optional) -```bash -./vendor/bin/sail artisan tinker ->>> $resolver = app(\App\Services\Intune\SettingsCatalogDefinitionResolver::class); ->>> $resolver->warmCache([...definitionIds...]); -``` - -### 3. Clear Caches -```bash -./vendor/bin/sail artisan optimize:clear -``` - -### 4. Verify -- Navigate to Policy View -- Check browser console for errors -- Check Laravel logs for Graph API errors - -## Rollback Plan - -### If Critical Issues Found -1. Revert database migration: - ```bash - ./vendor/bin/sail artisan migrate:rollback - ``` - -2. Revert code changes (Git): - ```bash - git revert - ``` - -3. Clear caches: - ```bash - ./vendor/bin/sail artisan optimize:clear - ``` - -### Partial Rollback -- Remove tabs, keep existing table view -- Disable definition resolver, show raw IDs -- Keep database table for future use - -## Dependencies on Feature 002 - -**Shared**: -- `pepperfm/filament-json` package (installed) -- JSON viewer CSS assets (published) -- Tab component pattern (Filament Schemas) - -**Independent**: -- Feature 185 can work without Feature 002 completed -- Feature 002 provided JSON tab foundation -- Feature 185 adds Settings tab with readable UI - -## Timeline Estimate - -- **Phase 1** (Foundation): 2-3 hours -- **Phase 2** (Snapshot): 1 hour -- **Phase 3** (Normalizer): 2-3 hours -- **Phase 4** (UI): 3-4 hours -- **Phase 5** (Testing): 2-3 hours -- **Total**: ~11-15 hours - -## Success Metrics - -1. **User Experience**: - - Admins can read policy settings without raw JSON - - Search finds settings in <200ms - - Accordion groups reduce scrolling - -2. **Performance**: - - Definition resolution: <500ms for 50 definitions - - UI render: <2s for 200 settings - - Search response: <200ms - -3. **Quality**: - - 100% test coverage for resolver - - Zero broken layouts for missing definitions - - Zero Graph API errors logged (with proper retry) - -4. **Adoption**: - - Settings tab used >80% of time vs JSON tab - - Zero support tickets about "unreadable settings" diff --git a/specs/185-settings-catalog-readable/spec.md b/specs/185-settings-catalog-readable/spec.md deleted file mode 100644 index 65fbba7..0000000 --- a/specs/185-settings-catalog-readable/spec.md +++ /dev/null @@ -1,240 +0,0 @@ -# Feature 185: Intune-like "Cleartext Settings" on Policy View - -## Overview -Display Settings Catalog policies in Policy View with human-readable setting names, descriptions, and formatted values—similar to Intune Portal experience—instead of raw JSON and definition IDs. - -## Problem Statement -Admins cannot effectively work with Settings Catalog policies when they only see: -- `settingDefinitionId` strings (e.g., `device_vendor_msft_passportforwork_biometrics_usebiometrics`) -- Raw JSON structures -- Choice values as GUIDs or internal strings - -This makes policy review, audit, and troubleshooting extremely difficult. - -## Goals -- **Primary**: Render Settings Catalog policies with display names, descriptions, grouped settings, and formatted values -- **Secondary**: Keep raw JSON available for audit/restore workflows -- **Tertiary**: Gracefully degrade when definition metadata is unavailable - -## User Stories - -### P1: US-UI-04 - Admin Views Readable Settings -**As an** Intune admin -**I want to** see policy settings with human-readable names and descriptions -**So that** I can understand what the policy configures without reading raw JSON - -**Acceptance Criteria:** -- Display name shown for each setting (not definition ID) -- Description/help text visible on hover or expand -- Values formatted appropriately (True/False, numbers, choice labels) -- Settings grouped by category/section - -### P2: US-UI-05 - Admin Searches/Filters Settings -**As an** Intune admin -**I want to** search and filter settings by name or value -**So that** I can quickly find specific configurations in large policies - -**Acceptance Criteria:** -- Search box filters settings list -- Search works on display name and value -- Results update instantly -- Clear search resets view - -### P3: US-UI-06 - Admin Accesses Raw JSON When Needed -**As an** Intune admin or auditor -**I want to** switch to raw JSON view -**So that** I can see the exact Graph API payload for audit/restore - -**Acceptance Criteria:** -- Tab navigation between "Settings" and "JSON" views -- JSON view shows complete policy snapshot -- JSON view includes copy-to-clipboard -- Settings view is default - -## Functional Requirements - -### FR-185.1: Setting Definition Resolver Service -- **Input**: Array of `settingDefinitionId` (including children from group settings) -- **Output**: Map of `{definitionId => {displayName, description, helpText, categoryId, uxBehavior, ...}}` -- **Strategy**: - - Fetch from Graph API settingDefinitions endpoints - - Cache in database (`settings_catalog_definitions` table) - - Memory cache for request-level performance - - Fallback to prettified ID if definition not found - -### FR-185.2: Database Schema for Definition Cache -**Table**: `settings_catalog_definitions` -- `id` (bigint, PK) -- `definition_id` (string, unique, indexed) -- `display_name` (string) -- `description` (text, nullable) -- `help_text` (text, nullable) -- `category_id` (string, nullable) -- `ux_behavior` (string, nullable) -- `raw` (jsonb) - full Graph response -- `timestamps` - -### FR-185.3: Snapshot Enrichment (Non-Blocking) -- After hydrating `/configurationPolicies/{id}/settings` -- Extract all `settingDefinitionId` + children -- Call resolver to warm cache -- Store render hints in snapshot metadata: `definitions_cached: true/false`, `definition_count: N` - -### FR-185.4: PolicyNormalizer Enhancement -- For `settingsCatalogPolicy` type: - - Output: `settings_groups[]` = `{title, description?, rows[]}` - - Each row: `{label, helpText?, value_display, value_raw, definition_id, instance_type}` - - Value formatting: - - `integer/bool`: show compact (True/False, numbers) - - `choice`: show friendly choice label (extract from `@odata.type` or value tail) - - `string`: truncate long values, add copy button - - Fallback: prettify `definitionId` if definition not found (e.g., `device_vendor_msft_policy_name` → "Device Vendor Msft Policy Name") - -### FR-185.5: Policy View UI Update -- **Layout**: 2-column - - Left: "Configuration Settings" (grouped, searchable) - - Right: "Policy Details" (existing metadata: name, type, platform, last synced) -- **Tabs**: - - "Settings" (default) - cleartext UI with accordion groups - - "JSON" - raw snapshot viewer (pepperfm/filament-json) -- **Search/Filter**: Live search on setting display name and value -- **Accordion**: Settings grouped by category, collapsible -- **Fallback**: Generic table for non-Settings Catalog policies (existing behavior) - -### FR-185.6: JSON Viewer Integration -- Use `pepperfm/filament-json` only on Policy View and Policy Version View -- Not rendered globally - -## Non-Functional Requirements - -### NFR-185.1: Performance -- Definition resolver: <500ms for batch of 50 definitions (cached) -- UI render: <2s for policy with 200 settings -- Search/filter: <200ms response time - -### NFR-185.2: Caching Strategy -- DB cache: 30 days TTL for definitions -- Memory cache: Request-level only -- Cache warming: Background job after policy sync (optional) - -### NFR-185.3: Graceful Degradation -- If definition not found: show prettified ID -- If Graph API fails: show cached data or fallback -- If no cache: show raw definition ID with info icon - -### NFR-185.4: Maintainability -- Resolver service isolated, testable -- Normalizer logic separated from UI -- UI components reusable for Version view - -## Technical Architecture - -### Services -1. **SettingsCatalogDefinitionResolver** (`app/Services/Intune/`) - - `resolve(array $definitionIds): array` - - `resolveOne(string $definitionId): ?array` - - `warmCache(array $definitionIds): void` - - Uses GraphClientInterface - - Database: `SettingsCatalogDefinition` model - -2. **PolicyNormalizer** (extend existing) - - `normalizeSettingsCatalog(array $snapshot, array $definitions): array` - - Returns structured groups + rows - -### Database -**Migration**: `create_settings_catalog_definitions_table` -**Model**: `SettingsCatalogDefinition` (Eloquent) - -### UI Components -**Resource**: `PolicyResource` (extend infolist) -- Tabs component -- Accordion for groups -- Search/filter component -- ViewEntry for settings table - -## Implementation Plan - -### Phase 1: Foundation (Resolver + DB) -1. Create migration `settings_catalog_definitions` -2. Create model `SettingsCatalogDefinition` -3. Create service `SettingsCatalogDefinitionResolver` -4. Add Graph client method for fetching definitions -5. Implement cache logic (DB + memory) - -### Phase 2: Snapshot Enrichment -1. Extend `PolicySnapshotService` to extract definition IDs -2. Call resolver after settings hydration -3. Store metadata in snapshot - -### Phase 3: Normalizer Enhancement -1. Extend `PolicyNormalizer` for Settings Catalog -2. Implement value formatting logic -3. Implement grouping logic -4. Add fallback for missing definitions - -### Phase 4: UI Implementation -1. Update `PolicyResource` infolist with tabs -2. Create accordion view for settings groups -3. Add search/filter functionality -4. Integrate JSON viewer (pepperfm) -5. Add fallback for non-Settings Catalog policies - -### Phase 5: Testing & Polish -1. Unit tests for resolver -2. Feature tests for UI -3. Manual QA on staging -4. Performance profiling - -## Testing Strategy - -### Unit Tests -- `SettingsCatalogDefinitionResolverTest` - - Test definition mapping - - Test caching behavior - - Test fallback logic - - Test batch resolution - -### Feature Tests -- `PolicyViewSettingsCatalogReadableTest` - - Mock Graph responses - - Assert UI shows display names - - Assert values formatted correctly - - Assert grouping works - - Assert search/filter works - - Assert JSON tab available - -## Success Criteria - -1. ✅ Admin sees human-readable setting names + descriptions -2. ✅ Values formatted appropriately (True/False, numbers, choice labels) -3. ✅ Settings grouped by category with accordion -4. ✅ Search/filter works on display name and value -5. ✅ Raw JSON available in separate tab -6. ✅ Unknown settings show prettified ID (no broken layout) -7. ✅ Performance: <2s render for 200 settings -8. ✅ Tests pass: Unit + Feature - -## Dependencies -- Existing: `PolicyNormalizer`, `PolicySnapshotService`, `GraphClientInterface` -- New: `pepperfm/filament-json` (already installed in Feature 002) -- Database: PostgreSQL with JSONB support - -## Risks & Mitigations -- **Risk**: Graph API rate limiting when fetching definitions - - **Mitigation**: Aggressive caching, batch requests, background warming -- **Risk**: Definition schema changes by Microsoft - - **Mitigation**: Raw JSONB storage allows flexible parsing, version metadata -- **Risk**: Large policies (1000+ settings) slow UI - - **Mitigation**: Pagination, lazy loading accordion groups, virtualized lists - -## Out of Scope -- Editing settings (read-only view only) -- Definition schema versioning -- Multi-language support for definitions -- Real-time definition updates (cache refresh manual/scheduled) - -## Future Enhancements -- Background job to pre-warm definition cache -- Definition schema versioning -- Comparison view between policy versions (diff) -- Export settings to CSV/Excel diff --git a/specs/185-settings-catalog-readable/tasks.md b/specs/185-settings-catalog-readable/tasks.md deleted file mode 100644 index 556b5c1..0000000 --- a/specs/185-settings-catalog-readable/tasks.md +++ /dev/null @@ -1,472 +0,0 @@ -# Feature 185: Settings Catalog Readable UI - Tasks - -## Summary -- **Total Tasks**: 42 -- **User Stories**: 3 (US-UI-04, US-UI-05, US-UI-06) -- **Estimated Time**: 11-15 hours -- **Phases**: 7 - -## FR→Task Traceability - -| FR | Description | Tasks | -|----|-------------|-------| -| FR-185.1 | Setting Definition Resolver Service | T003, T004, T005, T006, T007 | -| FR-185.2 | Database Schema | T001, T002 | -| FR-185.3 | Snapshot Enrichment | T008, T009, T010 | -| FR-185.4 | PolicyNormalizer Enhancement | T011, T012, T013, T014 | -| FR-185.5 | Policy View UI Update | T015-T024 | -| FR-185.6 | JSON Viewer Integration | T025 | - -## User Story→Task Mapping - -| User Story | Tasks | Success Criteria | -|------------|-------|------------------| -| US-UI-04 (Readable Settings) | T015-T020 | Display names shown, values formatted, grouped by category | -| US-UI-05 (Search/Filter) | T021, T022 | Search box works, filters settings, instant results | -| US-UI-06 (Raw JSON Access) | T023, T024, T025 | Tabs present, JSON view works, copy button functional | - -## Measurable Thresholds -- **Definition Resolution**: <500ms for batch of 50 definitions (cached) -- **UI Render**: <2s for policy with 200 settings -- **Search Response**: <200ms filter update -- **Database Cache TTL**: 30 days - ---- - -## Phase 1: Database Foundation (T001-T002) - -**Goal**: Create database schema for caching setting definitions - -- [X] **T001** Create migration for `settings_catalog_definitions` table - - Schema: id, definition_id (unique), display_name, description, help_text, category_id, ux_behavior, raw (jsonb), timestamps - - Indexes: definition_id (unique), category_id, raw (GIN) - - File: `database/migrations/2025_12_13_212126_create_settings_catalog_definitions_table.php` - - **Implementation Note**: Created migration with GIN index for JSONB, ran successfully - -- [X] **T002** Create `SettingsCatalogDefinition` Eloquent model - - Casts: raw → array - - Fillable: definition_id, display_name, description, help_text, category_id, ux_behavior, raw - - File: `app/Models/SettingsCatalogDefinition.php` - - **Implementation Note**: Added helper methods findByDefinitionId() and findByDefinitionIds() for efficient lookups - ---- - -## Phase 2: Definition Resolver Service (T003-T007) - -**Goal**: Implement service to fetch and cache setting definitions from Graph API - -**User Story**: US-UI-04 (foundation) - -- [X] **T003** [P] Create `SettingsCatalogDefinitionResolver` service skeleton - - Constructor: inject GraphClientInterface, SettingsCatalogDefinition model - - Methods: resolve(), resolveOne(), warmCache(), clearCache() - - File: `app/Services/Intune/SettingsCatalogDefinitionResolver.php` - - **Implementation Note**: Complete service with 3-tier caching (memory → DB → Graph API) - -- [X] **T004** [P] [US1] Implement `resolve(array $definitionIds): array` method - - Check memory cache (Laravel Cache) - - Check database cache - - Batch fetch missing from Graph API: `/deviceManagement/configurationSettings?$filter=id in (...)` - - Store in DB + memory cache - - Return map: `{definitionId => {displayName, description, ...}}` - - File: `app/Services/Intune/SettingsCatalogDefinitionResolver.php` - - **Implementation Note**: Implemented with batch request optimization and error handling - -- [X] **T005** [P] [US1] Implement `resolveOne(string $definitionId): ?array` method - - Single definition lookup - - Same caching strategy as resolve() - - Return null if not found - - File: `app/Services/Intune/SettingsCatalogDefinitionResolver.php` - - **Implementation Note**: Wraps resolve() for single ID lookup - -- [X] **T006** [US1] Implement fallback logic for missing definitions - - Prettify definition ID: `device_vendor_msft_policy_name` → "Device Vendor Msft Policy Name" - - Return fallback structure: `{displayName: prettified, description: null, ...}` - - File: `app/Services/Intune/SettingsCatalogDefinitionResolver.php` - - **Implementation Note**: prettifyDefinitionId() method with Str::title() conversion, isFallback flag added - -- [X] **T007** [P] Implement `warmCache(array $definitionIds): void` method - - Pre-populate cache without returning data - - Non-blocking: catch and log Graph API errors - - File: `app/Services/Intune/SettingsCatalogDefinitionResolver.php` - - **Implementation Note**: Non-blocking implementation with try/catch, logs warnings on failure - ---- - -## Phase 3: Snapshot Enrichment (T008-T010) - -**Goal**: Extend PolicySnapshotService to warm definition cache after settings hydration - -**User Story**: US-UI-04 (foundation) - -- [X] **T008** [US1] Extend `PolicySnapshotService` to extract definition IDs - - After hydrating `/configurationPolicies/{id}/settings` - - Extract all `settingDefinitionId` from settings array - - Include children from `groupSettingCollectionInstance` - - File: `app/Services/Intune/PolicySnapshotService.php` - - **Implementation Note**: Added extractDefinitionIds() method with recursive extraction from nested children - -- [X] **T009** [US1] Call SettingsCatalogDefinitionResolver::warmCache() in snapshot flow - - Pass extracted definition IDs to resolver - - Non-blocking: use try/catch for Graph API calls - - File: `app/Services/Intune/PolicySnapshotService.php` - - **Implementation Note**: Integrated warmCache() call in hydrateSettingsCatalog() after settings extraction - -- [X] **T010** [US1] Add metadata to snapshot about definition cache status - - Add to snapshot: `definitions_cached: true/false`, `definition_count: N` - - Store with snapshot data - - File: `app/Services/Intune/PolicySnapshotService.php` - - **Implementation Note**: Added definitions_cached and definition_count to metadata - ---- - -## Phase 4: PolicyNormalizer Enhancement (T011-T014) - -**Goal**: Transform Settings Catalog snapshots into UI-ready grouped structure - -**User Story**: US-UI-04 - -- [X] **T011** [US1] Create `normalizeSettingsCatalogGrouped()` method in PolicyNormalizer - - Input: array $snapshot, array $definitions - - Output: array with groups[] structure - - Extract settings from snapshot - - Resolve definitions for all setting IDs - - File: `app/Services/Intune/PolicyNormalizer.php` - - **Implementation Note**: Complete method with definition resolution integration - -- [X] **T012** [US1] Implement value formatting logic - - ChoiceSettingInstance: Extract choice label from @odata.type or value - - SimpleSetting (bool): "Enabled" / "Disabled" - - SimpleSetting (int): Number formatted with separators - - SimpleSetting (string): Truncate >100 chars, add "..." - - File: `app/Services/Intune/PolicyNormalizer.php` - - **Implementation Note**: Added formatSettingsCatalogValue() method with all formatting rules - -- [X] **T013** [US1] Implement grouping logic by category - - Group settings by categoryId from definition metadata - - Fallback: Group by first segment of definition ID - - Sort groups alphabetically by title - - File: `app/Services/Intune/PolicyNormalizer.php` - - **Implementation Note**: Added groupSettingsByCategory() with fallback extraction from definition IDs - -- [X] **T014** [US1] Implement nested group flattening for groupSettingCollectionInstance - - Recursively extract children from group settings - - Maintain hierarchy in output structure - - Include parent context in child labels - - File: `app/Services/Intune/PolicyNormalizer.php` - - **Implementation Note**: Recursive walk function handles nested children and group collections - ---- - -## Phase 5: UI Implementation - Settings Tab (T015-T022) - -**Goal**: Create readable Settings Catalog UI with accordion, search, and formatting - -**User Stories**: US-UI-04, US-UI-05 - -- [X] **T015** [US1] Add Tabs component to PolicyResource infolist for settingsCatalogPolicy - - Conditional rendering: only for settingsCatalogPolicy type - - Tab 1: "Settings" (default) - - Tab 2: "JSON" (existing from Feature 002) - - File: `app/Filament/Resources/PolicyResource.php` - - **Implementation Note**: Tabs already exist from Feature 002, extended Settings tab with grouped view - -- [X] **T016** [US1] Create Settings tab schema with search input - - TextInput for search/filter at top - - Placeholder: "Search settings..." - - Wire with Livewire for live filtering - - File: `app/Filament/Resources/PolicyResource.php` - - **Implementation Note**: Added search_info TextEntry (hidden for MVP), search implemented in Blade template - -- [X] **T017** [US1] Create Blade component for grouped settings accordion - - File: `resources/views/filament/infolists/entries/settings-catalog-grouped.blade.php` - - Props: groups (from normalizer), searchQuery - - Render accordion with Filament Section components - - **Implementation Note**: Complete Blade component with Filament Section integration - -- [X] **T018** [US1] Implement accordion group rendering - - Each group: Section with title + description - - Collapsible by default (first group expanded) - - Group header shows setting count - - File: `settings-catalog-grouped.blade.php` - - **Implementation Note**: Using x-filament::section with collapsible, first group expanded by default - -- [X] **T019** [US1] Implement setting row rendering within groups - - Layout: Label (bold) | Value (formatted) | Help icon - - Help icon: Tooltip with description + helpText - - Copy button for long values - - File: `settings-catalog-grouped.blade.php` - - **Implementation Note**: Flexbox layout with label, help text, value display, and Alpine.js copy button - -- [X] **T020** [US1] Add value formatting in Blade template - - Bool: Badge (Enabled/Disabled with colors) - - Int: Formatted number - - String: Truncate with "..." and expand button - - Choice: Show choice label - - File: `settings-catalog-grouped.blade.php` - - **Implementation Note**: Conditional rendering based on value type, badges for bool, monospace for int - -- [X] **T021** [US2] Implement search/filter logic in Livewire component - - Filter groups and settings by search query - - Search on display_name and value_display - - Update accordion to show only matching settings - - File: `app/Filament/Resources/PolicyResource.php` (or custom Livewire component) - - **Implementation Note**: Blade-level filtering using searchQuery prop, no Livewire component needed for MVP - -- [X] **T022** [US2] Add "No results" empty state for search - - Show message when search returns no matches - - Provide "Clear search" button - - File: `settings-catalog-grouped.blade.php` - - **Implementation Note**: Empty state with clear search button using wire:click - ---- - -## Phase 6: UI Implementation - Tabs & Fallback (T023-T025) - -**Goal**: Complete tab navigation and handle non-Settings Catalog policies - -**User Story**: US-UI-06 - -- [ ] **T023** [US3] Verify JSON tab still works (from Feature 002) - - Tab navigation switches correctly - - JSON viewer renders snapshot - - Copy button functional - - File: `app/Filament/Resources/PolicyResource.php` - -- [ ] **T024** [US3] Add fallback for policies without cached definitions - - Show info message in Settings tab: "Definitions not cached. Showing raw data." - - Display raw definition IDs with prettified labels - - Link to "View JSON" tab - - File: `settings-catalog-grouped.blade.php` - -- [ ] **T025** Ensure JSON viewer only renders on Policy View (not globally) - - Check existing implementation from Feature 002 - - Verify pepperfm/filament-json scoped correctly - - File: `app/Filament/Resources/PolicyResource.php` - ---- - -## Phase 7: Testing & Validation (T026-T042) - -**Goal**: Comprehensive testing for resolver, normalizer, and UI - -### Unit Tests (T026-T031) - -- [ ] **T026** [P] Create `SettingsCatalogDefinitionResolverTest` test file - - File: `tests/Unit/SettingsCatalogDefinitionResolverTest.php` - - Setup: Mock GraphClientInterface, in-memory database - -- [ ] **T027** [P] Test `resolve()` method with batch of definition IDs - - Assert: Returns map with display names - - Assert: Caches in database - - Assert: Uses cached data on second call - - File: `tests/Unit/SettingsCatalogDefinitionResolverTest.php` - -- [ ] **T028** [P] Test fallback logic for missing definitions - - Mock: Graph API returns 404 - - Assert: Returns prettified definition ID - - Assert: No exception thrown - - File: `tests/Unit/SettingsCatalogDefinitionResolverTest.php` - -- [ ] **T029** [P] Create `PolicyNormalizerSettingsCatalogTest` test file - - File: `tests/Unit/PolicyNormalizerSettingsCatalogTest.php` - - Setup: Mock definition data, sample snapshot - -- [ ] **T030** [P] Test grouping logic in normalizer - - Input: Snapshot with settings from different categories - - Assert: Groups created correctly - - Assert: Groups sorted alphabetically - - File: `tests/Unit/PolicyNormalizerSettingsCatalogTest.php` - -- [ ] **T031** [P] Test value formatting in normalizer - - Test bool → "Enabled"/"Disabled" - - Test int → formatted number - - Test string → truncation - - Test choice → label extraction - - File: `tests/Unit/PolicyNormalizerSettingsCatalogTest.php` - -### Feature Tests (T032-T037) - -- [ ] **T032** [P] Create `PolicyViewSettingsCatalogReadableTest` test file - - File: `tests/Feature/Filament/PolicyViewSettingsCatalogReadableTest.php` - - Setup: Mock GraphClient, create test policy with Settings Catalog type - -- [ ] **T033** Test Settings Catalog policy view shows tabs - - Navigate to Policy View - - Assert: Tabs component present - - Assert: "Settings" and "JSON" tabs visible - - File: `tests/Feature/Filament/PolicyViewSettingsCatalogReadableTest.php` - -- [ ] **T034** Test Settings tab shows display names (not definition IDs) - - Mock: Definitions cached - - Assert: Display names shown in UI - - Assert: Definition IDs NOT visible - - File: `tests/Feature/Filament/PolicyViewSettingsCatalogReadableTest.php` - -- [ ] **T035** Test values formatted correctly - - Mock: Settings with bool, int, string, choice values - - Assert: Bool shows "Enabled"/"Disabled" - - Assert: Int shows formatted number - - Assert: String shows truncated value - - File: `tests/Feature/Filament/PolicyViewSettingsCatalogReadableTest.php` - -- [ ] **T036** [US2] Test search/filter functionality - - Input: Type search query - - Assert: Settings list filtered - - Assert: Only matching settings shown - - Assert: Clear search resets view - - File: `tests/Feature/Filament/PolicyViewSettingsCatalogReadableTest.php` - -- [ ] **T037** Test graceful degradation for missing definitions - - Mock: Definitions not cached - - Assert: Fallback labels shown (prettified IDs) - - Assert: No broken layout - - Assert: Info message visible - - File: `tests/Feature/Filament/PolicyViewSettingsCatalogReadableTest.php` - -### Validation & Polish (T038-T042) - -- [ ] **T038** Run Pest test suite for Feature 185 - - Command: `./vendor/bin/sail artisan test --filter=SettingsCatalog` - - Assert: All tests pass - - Fix any failures - -- [ ] **T039** Run Laravel Pint on modified files - - Command: `./vendor/bin/sail pint --dirty` - - Assert: No style issues - - Commit fixes - -- [ ] **T040** Review git changes for Feature 185 - - Check: No changes to forbidden areas (see constitution) - - Verify: Only expected files modified - - Document: List of changed files in research.md - -- [ ] **T041** Run database migration on local environment - - Command: `./vendor/bin/sail artisan migrate` - - Verify: `settings_catalog_definitions` table created - - Check: Indexes applied correctly - -- [ ] **T042** Manual QA: Policy View with Settings Catalog policy - - Navigate to Policy View for Settings Catalog policy - - Verify: Tabs present ("Settings" and "JSON") - - Verify: Settings tab shows accordion with groups - - Verify: Display names shown (not raw IDs) - - Verify: Values formatted correctly - - Test: Search filters settings - - Test: JSON tab works - - Test: Copy buttons functional - - Test: Dark mode toggle - ---- - -## Dependencies & Execution Order - -### Sequential Dependencies -- **Phase 1** → **Phase 2**: Database must exist before resolver can cache -- **Phase 2** → **Phase 3**: Resolver must exist before snapshot enrichment -- **Phase 2** → **Phase 4**: Definitions needed for normalizer -- **Phase 4** → **Phase 5**: Normalized data structure needed for UI -- **Phase 5** → **Phase 7**: UI must exist before feature tests - -### Parallel Opportunities -- **Phase 2** (T003-T007): Resolver methods can be implemented in parallel -- **Phase 4** (T011-T014): Normalizer sub-methods can be implemented in parallel -- **Phase 5** (T015-T022): UI components can be developed in parallel after T015 -- **Phase 7** (T026-T031): Unit tests can be written in parallel -- **Phase 7** (T032-T037): Feature tests can be written in parallel - -### Example Parallel Execution -**Phase 2**: -- Developer A: T003, T004 (resolve methods) -- Developer B: T005, T006 (resolveOne + fallback) -- Both converge for T007 (warmCache) - -**Phase 5**: -- Developer A: T015-T017 (tabs + accordion setup) -- Developer B: T018-T020 (rendering logic) -- Both converge for T021-T022 (search functionality) - ---- - -## Task Complexity Estimates - -| Phase | Task Count | Estimated Time | Dependencies | -|-------|------------|----------------|--------------| -| Phase 1: Database | 2 | ~30 min | None | -| Phase 2: Resolver | 5 | ~2-3 hours | Phase 1 | -| Phase 3: Snapshot | 3 | ~1 hour | Phase 2 | -| Phase 4: Normalizer | 4 | ~2-3 hours | Phase 2 | -| Phase 5: UI Settings | 8 | ~3-4 hours | Phase 4 | -| Phase 6: UI Tabs | 3 | ~1 hour | Phase 5 | -| Phase 7: Testing | 17 | ~3-4 hours | Phase 2-6 | -| **Total** | **42** | **11-15 hours** | | - ---- - -## Success Criteria Checklist - -- [X] **SC-001**: Admin sees human-readable setting names (not definition IDs) on Policy View (Implementation complete - requires manual verification) -- [X] **SC-002**: Setting values formatted appropriately (True/False, numbers, choice labels) (Implementation complete - requires manual verification) -- [X] **SC-003**: Settings grouped by category with accordion (collapsible sections) (Implementation complete - requires manual verification) -- [X] **SC-004**: Search/filter works on display name and value (<200ms response) (Blade-level implementation complete - requires manual verification) -- [X] **SC-005**: Raw JSON available in separate "JSON" tab (Feature 002 integration preserved) -- [X] **SC-006**: Unknown settings show prettified ID fallback (no broken layout) (Implementation complete - requires manual verification) -- [ ] **SC-007**: Performance: <2s render for policy with 200 settings (Requires load testing) -- [ ] **SC-008**: Tests pass: Unit tests for resolver + normalizer (Tests not written yet) -- [ ] **SC-009**: Tests pass: Feature tests for UI rendering (Tests not written yet) -- [ ] **SC-010**: Definition resolution: <500ms for batch of 50 (cached) (Requires benchmark testing) - ---- - -## Constitution Compliance Evidence - -| Principle | Evidence | Tasks | -|-----------|----------|-------| -| Safety-First | Read-only UI, no edit capabilities | All UI tasks | -| Immutable Versioning | Snapshot enrichment non-blocking, metadata only | T008-T010 | -| Defensive Restore | Not applicable (read-only feature) | N/A | -| Auditability | Raw JSON still accessible via tab | T023-T025 | -| Tenant-Aware | Resolver respects tenant scoping (via GraphClient) | T003-T007 | -| Graph Abstraction | Uses existing GraphClientInterface | T003-T007 | -| Spec-Driven | Full spec + plan + tasks before implementation | This document | - ---- - -## Risk Mitigation Tasks - -- **Risk**: Graph API rate limiting - - **Mitigation**: T007 (warmCache is non-blocking), aggressive DB caching -- **Risk**: Definition schema changes by Microsoft - - **Mitigation**: T001 (raw JSONB storage), T006 (fallback logic) -- **Risk**: Large policies slow UI - - **Mitigation**: T017-T018 (accordion lazy-loading), performance tests in T042 - ---- - -## Notes for Implementation - -1. **Feature 002 Dependency**: Feature 185 uses tabs from Feature 002 JSON viewer implementation. Ensure Feature 002 code is stable before starting Phase 5. - -2. **Database Migration**: Run migration early (T001) to avoid blocking later phases. - -3. **Graph API Endpoints**: Verify access to `/deviceManagement/configurationSettings` endpoint in test environment before implementing T004. - -4. **Testing Strategy**: Write unit tests (Phase 7, T026-T031) in parallel with implementation to enable TDD workflow. - -5. **UI Polish**: Leave time for manual QA (T042) to catch UX issues not covered by automated tests. - -6. **Performance Profiling**: Use Laravel Telescope or Debugbar during T042 to measure actual performance vs NFR targets. - ---- - -## Implementation Readiness - -**Prerequisites**: -- ✅ Feature 002 JSON viewer implemented (tabs pattern established) -- ✅ pepperfm/filament-json installed -- ✅ GraphClientInterface available -- ✅ PolicyNormalizer exists -- ✅ PolicySnapshotService exists -- ✅ PostgreSQL with JSONB support - -**Ready to Start**: Phase 1 (Database Foundation)