TenantAtlas/app/Livewire/PolicyVersionAssignmentsWidget.php
ahmido 8ee1174c8d feat: add resolved reference presentation layer (#161)
## Summary
- add the shared resolved-reference foundation with registry, resolvers, presenters, and badge semantics
- refactor related context, assignment evidence, and policy-version assignment rendering toward label-first reference presentation
- add Spec 132 artifacts and focused Pest coverage for reference resolution, degraded states, canonical linking, and tenant-context carryover

## Verification
- `vendor/bin/sail bin pint --dirty --format agent`
- focused Pest verification was marked complete in the task artifact

## Notes
- this PR is opened from the current session branch
- `specs/132-guid-context-resolver/tasks.md` reflects in-progress completion state for the implemented tasks

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #161
2026-03-10 18:52:52 +00:00

225 lines
6.5 KiB
PHP

<?php
namespace App\Livewire;
use App\Models\PolicyVersion;
use App\Models\Tenant;
use App\Services\Directory\EntraGroupLabelResolver;
use App\Support\References\ReferencePresentationVariant;
use App\Support\References\ResolvedReferencePresenter;
use App\Support\References\Resolvers\AssignmentTargetReferenceResolver;
use Livewire\Component;
class PolicyVersionAssignmentsWidget extends Component
{
public PolicyVersion $version;
public function mount(PolicyVersion $version): void
{
$this->version = $version;
}
public function render(): \Illuminate\Contracts\View\View
{
return view('livewire.policy-version-assignments-widget', [
'version' => $this->version,
'compliance' => $this->complianceNotifications(),
'assignmentReferences' => $this->assignmentReferences(),
]);
}
/**
* @return array<int, array<string, mixed>>
*/
private function assignmentReferences(): array
{
$assignments = $this->version->assignments;
if (! is_array($assignments) || $assignments === []) {
return [];
}
$tenant = rescue(fn () => Tenant::current(), null);
$groupIds = [];
$sourceNames = [];
foreach ($assignments as $assignment) {
if (! is_array($assignment)) {
continue;
}
$target = $assignment['target'] ?? null;
if (! is_array($target)) {
continue;
}
$groupId = $target['groupId'] ?? null;
if (! is_string($groupId) || $groupId === '') {
continue;
}
$groupIds[] = $groupId;
$displayName = $target['group_display_name'] ?? null;
if (is_string($displayName) && $displayName !== '') {
$sourceNames[$groupId] = $displayName;
}
}
$groupIds = array_values(array_unique($groupIds));
$groupDescriptions = [];
if ($tenant instanceof Tenant && $groupIds !== []) {
$resolver = app(EntraGroupLabelResolver::class);
$groupDescriptions = $resolver->describeMany($tenant, $groupIds, $sourceNames);
} else {
foreach ($groupIds as $groupId) {
$groupDescriptions[$groupId] = [
'display_name' => $sourceNames[$groupId] ?? null,
'resolved' => false,
];
}
}
$references = [];
$assignmentTargetResolver = app(AssignmentTargetReferenceResolver::class);
$presenter = app(ResolvedReferencePresenter::class);
foreach ($assignments as $index => $assignment) {
if (! is_array($assignment)) {
continue;
}
$target = $assignment['target'] ?? null;
if (! is_array($target)) {
continue;
}
$type = strtolower((string) ($target['@odata.type'] ?? ''));
$targetId = (string) ($target['groupId'] ?? $target['collectionId'] ?? '');
$references[$index] = $presenter->present(
$assignmentTargetResolver->resolve($target, [
'tenant_id' => $tenant?->getKey(),
'target_type' => $type,
'target_id' => $targetId,
'group_descriptions' => $groupDescriptions,
'fallback_label' => is_string($target['group_display_name'] ?? null) ? $target['group_display_name'] : null,
]),
ReferencePresentationVariant::Compact,
);
}
return $references;
}
/**
* @return array{total:int,templates:array<int,string>,items:array<int,array{rule_name:?string,template_id:string,template_key:string}>}
*/
private function complianceNotifications(): array
{
if ($this->version->policy_type !== 'deviceCompliancePolicy') {
return [
'total' => 0,
'templates' => [],
'items' => [],
];
}
$snapshot = $this->version->snapshot;
if (! is_array($snapshot)) {
return [
'total' => 0,
'templates' => [],
'items' => [],
];
}
$scheduled = $snapshot['scheduledActionsForRule'] ?? null;
if (! is_array($scheduled)) {
return [
'total' => 0,
'templates' => [],
'items' => [],
];
}
$items = [];
$templateIds = [];
foreach ($scheduled as $rule) {
if (! is_array($rule)) {
continue;
}
$ruleName = $rule['ruleName'] ?? null;
$configs = $rule['scheduledActionConfigurations'] ?? null;
if (! is_array($configs)) {
continue;
}
foreach ($configs as $config) {
if (! is_array($config)) {
continue;
}
if (($config['actionType'] ?? null) !== 'notification') {
continue;
}
$templateKey = $this->resolveNotificationTemplateKey($config);
if ($templateKey === null) {
continue;
}
$templateId = $config[$templateKey] ?? null;
if (! is_string($templateId) || $templateId === '' || $this->isEmptyGuid($templateId)) {
continue;
}
$items[] = [
'rule_name' => is_string($ruleName) ? $ruleName : null,
'template_id' => $templateId,
'template_key' => $templateKey,
];
$templateIds[] = $templateId;
}
}
return [
'total' => count($items),
'templates' => array_values(array_unique($templateIds)),
'items' => $items,
];
}
private function resolveNotificationTemplateKey(array $config): ?string
{
if (array_key_exists('notificationTemplateId', $config)) {
return 'notificationTemplateId';
}
if (array_key_exists('notificationMessageTemplateId', $config)) {
return 'notificationMessageTemplateId';
}
return null;
}
private function isEmptyGuid(string $value): bool
{
return strtolower($value) === '00000000-0000-0000-0000-000000000000';
}
}