TenantAtlas/specs/003-settings-catalog-readable/tasks.md
2025-12-14 19:56:31 +01:00

473 lines
20 KiB
Markdown

# 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)