415 lines
13 KiB
Markdown
415 lines
13 KiB
Markdown
# 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 <commit-hash>
|
|
```
|
|
|
|
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"
|