TenantAtlas/app/Console/Commands/WarmSettingsCatalogDefinitionsCache.php
Ahmed Darrazi 58e6a4e980 feat(settings-catalog): Add category display and definition caching
- Add SettingsCatalogCategoryResolver service with 3-tier caching
- Add SettingsCatalogCategory model and migration
- Add warm-cache commands for definitions and categories
- Update PolicyNormalizer to display categories in settings table
- Fix extraction of nested children in choiceSettingValue
- Add category inheritance from parent settings
- Skip template IDs with {tenantid} placeholder in Graph API calls
- Update Livewire table with Category, Data Type, and Description columns

Related tests updated and passing.
2025-12-21 00:40:20 +01:00

184 lines
6.3 KiB
PHP

<?php
namespace App\Console\Commands;
use App\Models\Policy;
use App\Services\Intune\SettingsCatalogDefinitionResolver;
use Illuminate\Console\Command;
class WarmSettingsCatalogDefinitionsCache extends Command
{
protected $signature = 'intune:warm-definitions-cache
{--policy= : Specific policy ID to warm cache for}
{--all : Warm cache for all Settings Catalog policies}
{--force : Force re-fetch even if cached}';
protected $description = 'Warm the Settings Catalog definitions cache by fetching display names from Graph API';
public function handle(SettingsCatalogDefinitionResolver $resolver): int
{
if ($this->option('policy')) {
return $this->warmForPolicy($this->option('policy'), $resolver);
}
if ($this->option('all')) {
return $this->warmForAllPolicies($resolver);
}
$this->error('Please specify either --policy=ID or --all');
return self::FAILURE;
}
private function warmForPolicy(string $policyId, SettingsCatalogDefinitionResolver $resolver): int
{
$policy = Policy::find($policyId);
if (! $policy) {
$this->error("Policy {$policyId} not found");
return self::FAILURE;
}
if ($policy->policy_type !== 'settingsCatalog' && $policy->policy_type !== 'settingsCatalogPolicy') {
$this->error("Policy {$policyId} is not a Settings Catalog policy");
return self::FAILURE;
}
$this->info("Warming cache for policy: {$policy->display_name} ({$policy->id})");
$snapshot = $policy->versions()->latest('version_number')->first()?->snapshot ?? [];
$definitionIds = $this->extractDefinitionIds($snapshot);
if (empty($definitionIds)) {
$this->warn('No definition IDs found in policy snapshot');
return self::SUCCESS;
}
$this->info('Found '.count($definitionIds).' definition IDs');
$this->newLine();
$progressBar = $this->output->createProgressBar(count($definitionIds));
$progressBar->start();
$success = 0;
$failed = 0;
$cached = 0;
foreach ($definitionIds as $definitionId) {
try {
$definition = $resolver->resolveOne($definitionId);
if ($definition) {
if (isset($definition['displayName']) && ! str_contains($definition['displayName'], 'Device Vendor Msft')) {
$success++;
$this->line("\n{$definitionId}{$definition['displayName']}", 'info');
} else {
$cached++;
$this->line("\n{$definitionId} → (fallback: {$definition['displayName']})", 'comment');
}
} else {
$failed++;
$this->line("\n{$definitionId} → Failed to resolve", 'error');
}
} catch (\Exception $e) {
$failed++;
$this->line("\n{$definitionId} → Error: {$e->getMessage()}", 'error');
}
$progressBar->advance();
}
$progressBar->finish();
$this->newLine(2);
$this->table(
['Status', 'Count'],
[
['✓ Successfully fetched from Graph', $success],
['⚠ Using fallback (not in Graph)', $cached],
['✗ Failed', $failed],
['Total', count($definitionIds)],
]
);
return self::SUCCESS;
}
private function warmForAllPolicies(SettingsCatalogDefinitionResolver $resolver): int
{
$policies = Policy::where(function ($query) {
$query->where('policy_type', 'settingsCatalog')
->orWhere('policy_type', 'settingsCatalogPolicy');
})->get();
if ($policies->isEmpty()) {
$this->warn('No Settings Catalog policies found');
return self::SUCCESS;
}
$this->info("Found {$policies->count()} Settings Catalog policies");
$this->newLine();
foreach ($policies as $policy) {
$this->warmForPolicy((string) $policy->id, $resolver);
$this->newLine();
}
return self::SUCCESS;
}
private function extractDefinitionIds(array $snapshot): array
{
$ids = [];
$settings = $snapshot['settings'] ?? [];
$walk = function (array $nodes) use (&$walk, &$ids): void {
foreach ($nodes as $node) {
if (! is_array($node)) {
continue;
}
// Top-level settings have settingInstance wrapper
if (isset($node['settingInstance']['settingDefinitionId'])) {
$ids[] = $node['settingInstance']['settingDefinitionId'];
$instance = $node['settingInstance'];
}
// Nested children have settingDefinitionId directly
elseif (isset($node['settingDefinitionId'])) {
$ids[] = $node['settingDefinitionId'];
$instance = $node;
} else {
continue;
}
// Handle nested children in choice setting value
if (isset($instance['choiceSettingValue']['children']) && is_array($instance['choiceSettingValue']['children'])) {
$walk($instance['choiceSettingValue']['children']);
}
// Handle nested children in group collections
if (isset($instance['groupSettingCollectionValue'])) {
foreach ($instance['groupSettingCollectionValue'] as $group) {
if (isset($group['children']) && is_array($group['children'])) {
$walk($group['children']);
}
}
}
// Handle nested children in group setting value
if (isset($instance['groupSettingValue']['children']) && is_array($instance['groupSettingValue']['children'])) {
$walk($instance['groupSettingValue']['children']);
}
}
};
$walk($settings);
return array_unique($ids);
}
}