# Feature 003: 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-003.1 | Setting Definition Resolver Service | T003, T004, T005, T006, T007 | | FR-003.2 | Database Schema | T001, T002 | | FR-003.3 | Snapshot Enrichment | T008, T009, T010 | | FR-003.4 | PolicyNormalizer Enhancement | T011, T012, T013, T014 | | FR-003.5 | Policy View UI Update | T015-T024 | | FR-003.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 003 - 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 003 - 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 003 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)