resolveScopeTagNames($scopeTagIds, $tenant); $metadata = $backupItem->metadata ?? []; $metadata['scope_tag_ids'] = $scopeTagIds; $metadata['scope_tag_names'] = $scopeTagNames; // Only fetch assignments if explicitly requested if (! $includeAssignments) { $metadata['assignment_count'] = 0; $backupItem->update([ 'assignments' => null, 'metadata' => $metadata, ]); return $backupItem->refresh(); } // Fetch assignments from Graph API $graphOptions = $tenant->graphOptions(); $tenantId = $graphOptions['tenant'] ?? $tenant->external_id ?? $tenant->tenant_id; $assignments = $this->assignmentFetcher->fetch($tenantId, $policyId, $graphOptions); if (empty($assignments)) { // No assignments or fetch failed $metadata['assignment_count'] = 0; $metadata['assignments_fetch_failed'] = true; $metadata['has_orphaned_assignments'] = false; $backupItem->update([ 'assignments' => [], // Return empty array instead of null 'metadata' => $metadata, ]); Log::warning('No assignments fetched for policy', [ 'tenant_id' => $tenantId, 'policy_id' => $policyId, 'backup_item_id' => $backupItem->id, ]); return $backupItem->refresh(); } // Extract group IDs and resolve for orphan detection $groupIds = $this->extractGroupIds($assignments); $resolvedGroups = []; $hasOrphanedGroups = false; if (! empty($groupIds)) { $resolvedGroups = $this->groupResolver->resolveGroupIds($groupIds, $tenantId, $graphOptions); $hasOrphanedGroups = collect($resolvedGroups) ->contains(fn (array $group) => $group['orphaned'] ?? false); } $filterIds = collect($assignments) ->pluck('target.deviceAndAppManagementAssignmentFilterId') ->filter() ->unique() ->values() ->all(); $filters = $this->assignmentFilterResolver->resolve($filterIds, $tenant); $filterNames = collect($filters) ->pluck('displayName', 'id') ->all(); $assignments = $this->enrichAssignments($assignments, $resolvedGroups, $filterNames); // Update backup item with assignments and metadata $metadata['assignment_count'] = count($assignments); $metadata['assignments_fetch_failed'] = false; $metadata['has_orphaned_assignments'] = $hasOrphanedGroups; $backupItem->update([ 'assignments' => $assignments, 'metadata' => $metadata, ]); Log::info('Assignments enriched for backup item', [ 'tenant_id' => $tenantId, 'policy_id' => $policyId, 'backup_item_id' => $backupItem->id, 'assignment_count' => count($assignments), 'has_orphaned' => $hasOrphanedGroups, ]); return $backupItem->refresh(); } /** * Resolve scope tag IDs to display names. */ private function resolveScopeTagNames(array $scopeTagIds, Tenant $tenant): array { $scopeTags = $this->scopeTagResolver->resolve($scopeTagIds, $tenant); $names = []; foreach ($scopeTagIds as $id) { $scopeTag = collect($scopeTags)->firstWhere('id', $id); $names[] = $scopeTag['displayName'] ?? "Unknown (ID: {$id})"; } return $names; } /** * Extract group IDs from assignment array. */ private function extractGroupIds(array $assignments): array { $groupIds = []; foreach ($assignments as $assignment) { $target = $assignment['target'] ?? []; $odataType = $target['@odata.type'] ?? ''; if (in_array($odataType, [ '#microsoft.graph.groupAssignmentTarget', '#microsoft.graph.exclusionGroupAssignmentTarget', ], true) && isset($target['groupId'])) { $groupIds[] = $target['groupId']; } } return array_unique($groupIds); } /** * @param array> $assignments * @param array $groups * @param array $filterNames * @return array> */ private function enrichAssignments(array $assignments, array $groups, array $filterNames): array { return array_map(function (array $assignment) use ($groups, $filterNames): array { $target = $assignment['target'] ?? []; $groupId = $target['groupId'] ?? null; if ($groupId && isset($groups[$groupId])) { $target['group_display_name'] = $groups[$groupId]['displayName'] ?? null; $target['group_orphaned'] = $groups[$groupId]['orphaned'] ?? false; } $filterId = $target['deviceAndAppManagementAssignmentFilterId'] ?? null; if ($filterId && isset($filterNames[$filterId])) { $target['assignment_filter_name'] = $filterNames[$filterId]; } $assignment['target'] = $target; return $assignment; }, $assignments); } }