*/ protected function getViewData(): array { $tenant = Filament::getTenant(); $empty = [ 'hasAssignment' => false, 'profileName' => null, 'lastComparedAt' => null, 'landingUrl' => null, 'runUrl' => null, 'findingsUrl' => null, 'nextActionLabel' => null, 'nextActionUrl' => null, 'nextActionHelperText' => null, 'summaryAssessment' => null, ]; if (! $tenant instanceof Tenant) { return $empty; } $aggregate = $this->governanceAggregate($tenant); if ($aggregate->compareState === 'no_assignment') { return $empty; } $tenantLandingUrl = BaselineCompareLanding::getUrl(panel: 'tenant', tenant: $tenant); $operationsFollowUpCount = (int) OperationRun::query() ->where('tenant_id', (int) $tenant->getKey()) ->dashboardNeedsFollowUp() ->count(); $summaryAssessment = $this->dashboardSummaryAssessment($aggregate, $operationsFollowUpCount); $runUrl = $this->runUrl($tenant, $aggregate); $findingsUrl = $this->findingsUrl($tenant, $aggregate); $nextActionTarget = (string) ($summaryAssessment['dashboardNextActionTarget'] ?? (($summaryAssessment['nextAction']['target'] ?? 'none') ?: 'none')); $nextActionLabel = (string) ($summaryAssessment['nextAction']['label'] ?? ''); $nextActionUrl = match ($nextActionTarget) { 'run' => $runUrl, 'findings' => $findingsUrl, 'landing' => $tenantLandingUrl, 'operations' => OperationRunLinks::index($tenant, activeTab: 'blocked'), default => null, }; $nextActionHelperText = in_array($nextActionTarget, ['run', 'findings'], true) && $nextActionUrl === null ? UiTooltips::INSUFFICIENT_PERMISSION : null; return [ 'hasAssignment' => true, 'profileName' => $aggregate->profileName, 'lastComparedAt' => $aggregate->lastComparedLabel, 'landingUrl' => $tenantLandingUrl, 'runUrl' => $runUrl, 'findingsUrl' => $findingsUrl, 'nextActionLabel' => $nextActionLabel !== '' ? $nextActionLabel : null, 'nextActionUrl' => $nextActionUrl, 'nextActionHelperText' => $nextActionHelperText, 'summaryAssessment' => $summaryAssessment, ]; } /** * @return array */ private function dashboardSummaryAssessment(TenantGovernanceAggregate $aggregate, int $operationsFollowUpCount): array { $summaryAssessment = $aggregate->summaryAssessment->toArray(); if (($summaryAssessment['stateFamily'] ?? null) !== BaselineCompareSummaryAssessment::STATE_POSITIVE) { return $summaryAssessment; } if ($aggregate->highSeverityActiveFindingsCount > 0) { $count = $aggregate->highSeverityActiveFindingsCount; return array_merge($summaryAssessment, [ 'stateFamily' => BaselineCompareSummaryAssessment::STATE_ACTION_REQUIRED, 'tone' => 'danger', 'headline' => sprintf('%d high-severity active finding%s need review.', $count, $count === 1 ? '' : 's'), 'supportingMessage' => 'The latest compare may be healthy, but the tenant still has active high-severity findings.', 'highSeverityCount' => $count, 'nextAction' => [ 'label' => 'Open findings', 'target' => BaselineCompareSummaryAssessment::NEXT_TARGET_FINDINGS, ], 'dashboardNextActionTarget' => 'findings', ]); } if ($operationsFollowUpCount > 0) { return array_merge($summaryAssessment, [ 'stateFamily' => BaselineCompareSummaryAssessment::STATE_ACTION_REQUIRED, 'tone' => 'danger', 'headline' => sprintf('%d operation%s need follow-up.', $operationsFollowUpCount, $operationsFollowUpCount === 1 ? '' : 's'), 'supportingMessage' => 'Failed, warning, or stalled runs still need review before this tenant reads as fully calm.', 'nextAction' => [ 'label' => 'Open operations', 'target' => BaselineCompareSummaryAssessment::NEXT_TARGET_NONE, ], 'dashboardNextActionTarget' => 'operations', ]); } return $summaryAssessment; } private function runUrl(Tenant $tenant, TenantGovernanceAggregate $aggregate): ?string { $runId = $aggregate->stats->operationRunId; if (! is_int($runId)) { return null; } $run = OperationRun::query()->find($runId); if (! $run instanceof OperationRun || ! $this->canOpenRun($run)) { return null; } return OperationRunLinks::view($run, $tenant); } private function findingsUrl(Tenant $tenant, TenantGovernanceAggregate $aggregate): ?string { if (! $this->canOpenFindings($tenant)) { return null; } $parameters = match (true) { $aggregate->lapsedGovernanceCount > 0 => [ 'tab' => 'risk_accepted', 'governance_validity' => FindingException::VALIDITY_MISSING_SUPPORT, ], $aggregate->overdueOpenFindingsCount > 0 => [ 'tab' => 'overdue', ], $aggregate->expiringGovernanceCount > 0 => [ 'tab' => 'risk_accepted', 'governance_validity' => FindingException::VALIDITY_EXPIRING, ], $aggregate->highSeverityActiveFindingsCount > 0 => [ 'tab' => 'needs_action', 'high_severity' => 1, ], $aggregate->visibleDriftFindingsCount > 0 => [ 'tab' => 'needs_action', 'finding_type' => 'drift', ], default => [], }; return FindingResource::getUrl('index', $parameters, panel: 'tenant', tenant: $tenant); } private function canOpenFindings(Tenant $tenant): bool { $user = auth()->user(); return $user instanceof User && $user->canAccessTenant($tenant) && $user->can(Capabilities::TENANT_FINDINGS_VIEW, $tenant); } private function canOpenRun(OperationRun $run): bool { $user = auth()->user(); return $user instanceof User && $user->can('view', $run); } private function governanceAggregate(Tenant $tenant): TenantGovernanceAggregate { /** @var TenantGovernanceAggregateResolver $resolver */ $resolver = app(TenantGovernanceAggregateResolver::class); /** @var TenantGovernanceAggregate $aggregate */ $aggregate = $resolver->forTenant($tenant); return $aggregate; } }