TenantAtlas/specs/185-settings-catalog-readable/plan.md
2025-12-14 20:23:18 +01:00

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

[
    '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

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}
  1. 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

./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

  1. Revert database migration:

    ./vendor/bin/sail artisan migrate:rollback
    
  2. Revert code changes (Git):

    git revert <commit-hash>
    
  3. 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-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"