13 KiB
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 definitionsresolveOne(string $definitionId): ?array- Single definition lookupwarmCache(array $definitionIds): void- Pre-populate cacheclearCache(?string $definitionId = null): void- Cache invalidation
Dependencies:
GraphClientInterface- Graph API callsSettingsCatalogDefinitionmodel - Database cache- Laravel Cache - Memory-level cache
Caching Strategy:
- Check memory cache (request-level)
- Check database cache (30-day TTL)
- Fetch from Graph API
- 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:
[
'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.typeor valueSimpleSetting(bool): "Enabled" / "Disabled"SimpleSetting(int): Number formatted with separatorsSimpleSetting(string): Truncate >100 chars, add "..."GroupSettingCollectionInstance: Flatten children recursively
Grouping Strategy:
- Group by
categoryIdfrom 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: falseon failure - Continue with snapshot save
4. PolicyResource (UI Extension)
Purpose: Render Settings Catalog policies with readable UI
Changes:
-
Add Tabs component to infolist:
- "Settings" tab (default)
- "JSON" tab (existing Feature 002 implementation)
-
Settings Tab Structure:
- Search/filter input (top)
- Accordion component (groups)
- Each group: Section with settings table
- Fallback: Show info message if no definitions cached
-
JSON Tab:
- Existing implementation from Feature 002
- Shows full snapshot with copy button
Conditional Rendering:
- Show tabs ONLY for
settingsCatalogPolicytype - For other policy types: Keep existing simple sections
Database Schema
Table: settings_catalog_definitions
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 keycategory_id- Grouping queriesraw(GIN) - JSONB queries if needed
Graph API Integration
Endpoints Used
- Global Catalog (Preferred):
GET /deviceManagement/configurationSettings
GET /deviceManagement/configurationSettings/{settingDefinitionId}
- Policy-Specific (Fallback):
GET /deviceManagement/configurationPolicies/{policyId}/settings/{settingId}/settingDefinitions
Request Optimization
- Batch requests where possible
- Use
$selectto limit fields - Use
$filterfor targeted lookups - Respect rate limits (429 retry logic)
UI/UX Flow
Policy View Page Flow
- User navigates to
/admin/policies/{id} - Policy details loaded (existing)
- Check policy type:
- If
settingsCatalogPolicy: Show tabs - Else: Show existing sections
- If
- Default to "Settings" tab
- Load normalized settings from PolicyNormalizer
- Render accordion with groups
- User can search/filter settings
- 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_idensures 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.Allsufficient - 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
- Open Policy View for Settings Catalog policy
- Verify tabs present: "Settings" and "JSON"
- Verify Settings tab shows groups with accordion
- Verify display names shown (not raw IDs)
- Verify values formatted (True/False, numbers, etc.)
- Test search: Type setting name, verify filtering
- Switch to JSON tab, verify snapshot shown
- Test copy button in JSON tab
- Test dark mode toggle
- Test with policy missing definitions (fallback labels)
Deployment Steps
1. Database Migration
./vendor/bin/sail artisan migrate
2. Cache Warming (Optional)
./vendor/bin/sail artisan tinker
>>> $resolver = app(\App\Services\Intune\SettingsCatalogDefinitionResolver::class);
>>> $resolver->warmCache([...definitionIds...]);
3. Clear Caches
./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
-
Revert database migration:
./vendor/bin/sail artisan migrate:rollback -
Revert code changes (Git):
git revert <commit-hash> -
Clear caches:
./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-jsonpackage (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
-
User Experience:
- Admins can read policy settings without raw JSON
- Search finds settings in <200ms
- Accordion groups reduce scrolling
-
Performance:
- Definition resolution: <500ms for 50 definitions
- UI render: <2s for 200 settings
- Search response: <200ms
-
Quality:
- 100% test coverage for resolver
- Zero broken layouts for missing definitions
- Zero Graph API errors logged (with proper retry)
-
Adoption:
- Settings tab used >80% of time vs JSON tab
- Zero support tickets about "unreadable settings"