TenantAtlas/apps/platform/app/Filament/Support/VerificationReportViewer.php
ahmido e64bae9cfc feat: cut over tenant core to managed environments (#335)
## Summary
- replace the legacy Tenant and TenantMembership core models with ManagedEnvironment and ManagedEnvironmentMembership
- propagate the managed environment naming and key changes across Filament resources, pages, controllers, jobs, models, and supporting runtime paths
- add feature 279 spec artifacts and focused managed-environment test coverage for model behavior, route binding, panel context, authorization, and legacy guardrails

## Validation
- `cd apps/platform && ./vendor/bin/sail artisan test --compact tests/Feature/ManagedEnvironment/LegacyTenantCoreGuardTest.php tests/Feature/ManagedEnvironment/ManagedEnvironmentAuthorizationTest.php tests/Feature/ManagedEnvironment/ManagedEnvironmentPanelContextTest.php tests/Feature/ManagedEnvironment/ManagedEnvironmentRouteBindingTest.php tests/Unit/ManagedEnvironment/ManagedEnvironmentContextResolverTest.php tests/Unit/ManagedEnvironment/ManagedEnvironmentModelTest.php`
- `cd apps/platform && ./vendor/bin/sail bin pint --dirty --format agent`

## Notes
- branch pushed from commit `1123b122`
- browser smoke test file was added but not run in this pass

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #335
2026-05-07 06:38:14 +00:00

375 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
declare(strict_types=1);
namespace App\Filament\Support;
use App\Models\OperationRun;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use App\Support\RedactionIntegrity;
use App\Support\Verification\VerificationReportFingerprint;
use App\Support\Verification\VerificationReportSanitizer;
use App\Support\Verification\VerificationReportSchema;
final class VerificationReportViewer
{
/**
* @return array<string, mixed>|null
*/
public static function report(OperationRun $run): ?array
{
$context = is_array($run->context) ? $run->context : [];
$report = $context['verification_report'] ?? null;
if (! is_array($report)) {
return null;
}
$report = VerificationReportSanitizer::sanitizeReport($report);
if (! VerificationReportSchema::isValidReport($report)) {
return null;
}
return $report;
}
public static function previousReportId(array $report): ?int
{
$previousReportId = $report['previous_report_id'] ?? null;
if (is_int($previousReportId) && $previousReportId > 0) {
return $previousReportId;
}
if (is_string($previousReportId) && ctype_digit(trim($previousReportId))) {
return (int) trim($previousReportId);
}
return null;
}
public static function fingerprint(array $report): ?string
{
$fingerprint = $report['fingerprint'] ?? null;
if (is_string($fingerprint)) {
$fingerprint = strtolower(trim($fingerprint));
if (preg_match('/^[a-f0-9]{64}$/', $fingerprint)) {
return $fingerprint;
}
}
return VerificationReportFingerprint::forReport($report);
}
public static function previousRun(OperationRun $run, array $report): ?OperationRun
{
$previousReportId = self::previousReportId($report);
if ($previousReportId === null) {
return null;
}
$previous = OperationRun::query()
->whereKey($previousReportId)
->where('managed_environment_id', (int) $run->managed_environment_id)
->where('workspace_id', (int) $run->workspace_id)
->first();
return $previous instanceof OperationRun ? $previous : null;
}
public static function shouldRenderForRun(OperationRun $run): bool
{
$context = is_array($run->context) ? $run->context : [];
if (array_key_exists('verification_report', $context)) {
return true;
}
return in_array((string) $run->type, ['provider.connection.check'], true);
}
/**
* @param array<string, array<string, mixed>> $acknowledgements
* @param array{
* hostKind?: string,
* changeIndicator?: array{state: string, previous_report_id: int}|null,
* previousRunUrl?: string|null,
* nextStepPlacement?: 'shared_zone'|'host_action_zone',
* hostActions?: array<int, array{kind: string, label: string, ownedByHost: bool}>,
* hostVariation?: array{
* ownsNoRunState?: bool,
* ownsActiveState?: bool,
* supportsAssist?: bool,
* supportsAcknowledge?: bool,
* supportsTechnicalDetailsTrigger?: bool
* },
* optionalZones?: array<int, string>
* } $options
* @return array<string, mixed>
*/
public static function surface(OperationRun $run, array $acknowledgements = [], array $options = []): array
{
$report = self::report($run);
$summary = is_array($report['summary'] ?? null) ? $report['summary'] : [];
$counts = is_array($summary['counts'] ?? null) ? $summary['counts'] : [];
$groupedChecks = self::groupedChecks($report, $acknowledgements);
$changeIndicator = $options['changeIndicator'] ?? null;
$hostKind = is_string($options['hostKind'] ?? null) && $options['hostKind'] !== ''
? (string) $options['hostKind']
: 'operation_run_detail';
$nextStepPlacement = ($options['nextStepPlacement'] ?? 'shared_zone') === 'host_action_zone'
? 'host_action_zone'
: 'shared_zone';
$hostActions = collect($options['hostActions'] ?? [])
->filter(static fn (mixed $action): bool => is_array($action))
->map(static function (array $action): array {
$kind = is_string($action['kind'] ?? null) ? trim((string) $action['kind']) : 'navigation';
$label = is_string($action['label'] ?? null) ? trim((string) $action['label']) : 'Action';
return [
'kind' => $kind !== '' ? $kind : 'navigation',
'label' => $label !== '' ? $label : 'Action',
'ownedByHost' => (bool) ($action['ownedByHost'] ?? true),
];
})
->values()
->all();
$hostVariation = [
'ownsNoRunState' => (bool) (($options['hostVariation']['ownsNoRunState'] ?? false)),
'ownsActiveState' => (bool) (($options['hostVariation']['ownsActiveState'] ?? false)),
'supportsAssist' => (bool) (($options['hostVariation']['supportsAssist'] ?? false)),
'supportsAcknowledge' => (bool) (($options['hostVariation']['supportsAcknowledge'] ?? false)),
'supportsTechnicalDetailsTrigger' => (bool) (($options['hostVariation']['supportsTechnicalDetailsTrigger'] ?? false)),
];
$optionalZones = collect($options['optionalZones'] ?? ['technical_details', 'change_indicator', 'previous_run_context'])
->filter(static fn (mixed $zone): bool => is_string($zone) && trim($zone) !== '')
->map(static fn (string $zone): string => trim($zone))
->values()
->all();
$overall = $summary['overall'] ?? null;
$overallSpec = BadgeRenderer::spec(BadgeDomain::VerificationReportOverall, $overall);
return [
'hostKind' => $hostKind,
'coreState' => $report === null ? 'unavailable' : 'completed',
'summary' => [
'overall' => $overall,
'overallLabel' => $overallSpec->label,
'counts' => [
'total' => (int) ($counts['total'] ?? 0),
'pass' => (int) ($counts['pass'] ?? 0),
'fail' => (int) ($counts['fail'] ?? 0),
'warn' => (int) ($counts['warn'] ?? 0),
'skip' => (int) ($counts['skip'] ?? 0),
'running' => (int) ($counts['running'] ?? 0),
],
'changeIndicator' => is_array($changeIndicator) ? $changeIndicator : null,
],
'issueGroups' => $groupedChecks['issueGroups'],
'passedChecks' => $groupedChecks['passedChecks'],
'diagnostics' => [
'hasTechnicalZone' => true,
'fingerprint' => is_array($report) ? self::fingerprint($report) : null,
'previousRunUrl' => is_string($options['previousRunUrl'] ?? null) && $options['previousRunUrl'] !== ''
? (string) $options['previousRunUrl']
: null,
'operationRunId' => (int) $run->getKey(),
'flow' => (string) $run->type,
'completedAt' => $run->completed_at?->toJSON(),
],
'viewZones' => [
['key' => 'issues', 'label' => 'Issues', 'defaultVisible' => true],
['key' => 'passed', 'label' => 'Passed', 'defaultVisible' => false],
],
'nextSteps' => self::nextSteps($groupedChecks['issueGroups'], $nextStepPlacement),
'hostActions' => $hostActions,
'hostVariation' => $hostVariation,
'optionalZones' => $optionalZones,
'emptyState' => $report === null
? [
'title' => 'Verification report unavailable',
'message' => 'This operation doesnt have a report yet. If it is still running, refresh in a moment. If it already completed, start verification again.',
]
: null,
];
}
/**
* @param array<string, mixed>|null $report
* @param array<string, array<string, mixed>> $acknowledgements
* @return array{issueGroups: array<int, array{label: string, checks: array<int, array<string, mixed>>, acknowledged?: bool}>, passedChecks: array<int, array<string, mixed>>}
*/
private static function groupedChecks(?array $report, array $acknowledgements): array
{
$checks = is_array($report['checks'] ?? null) ? $report['checks'] : [];
$ackByKey = [];
foreach ($acknowledgements as $checkKey => $acknowledgement) {
if (! is_string($checkKey) || $checkKey === '' || ! is_array($acknowledgement)) {
continue;
}
$ackByKey[$checkKey] = $acknowledgement;
}
$blockers = [];
$failures = [];
$warnings = [];
$acknowledgedIssues = [];
$passed = [];
foreach ($checks as $check) {
if (! is_array($check)) {
continue;
}
$key = is_string($check['key'] ?? null) ? trim((string) $check['key']) : '';
if ($key === '') {
continue;
}
$status = is_string($check['status'] ?? null) ? strtolower(trim((string) $check['status'])) : '';
$blocking = (bool) ($check['blocking'] ?? false);
$normalizedCheck = self::normalizeCheck($check, $ackByKey[$key] ?? null);
if ($normalizedCheck['acknowledgement'] !== null) {
$acknowledgedIssues[] = $normalizedCheck;
continue;
}
if ($status === 'pass') {
$passed[] = $normalizedCheck;
continue;
}
if ($status === 'fail' && $blocking) {
$blockers[] = $normalizedCheck;
continue;
}
if ($status === 'fail') {
$failures[] = $normalizedCheck;
continue;
}
if ($status === 'warn') {
$warnings[] = $normalizedCheck;
}
}
$sortChecks = static fn (array $left, array $right): int => strcmp((string) ($left['key'] ?? ''), (string) ($right['key'] ?? ''));
usort($blockers, $sortChecks);
usort($failures, $sortChecks);
usort($warnings, $sortChecks);
usort($acknowledgedIssues, $sortChecks);
usort($passed, $sortChecks);
return [
'issueGroups' => array_values(array_filter([
['label' => 'Blockers', 'checks' => $blockers],
['label' => 'Failures', 'checks' => $failures],
['label' => 'Warnings', 'checks' => $warnings],
['label' => 'Acknowledged issues', 'checks' => $acknowledgedIssues, 'acknowledged' => true],
], static fn (array $group): bool => ($group['checks'] ?? []) !== [])),
'passedChecks' => $passed,
];
}
/**
* @param array<string, mixed> $check
* @param array<string, mixed>|null $acknowledgement
* @return array<string, mixed>
*/
private static function normalizeCheck(array $check, ?array $acknowledgement): array
{
$nextSteps = collect($check['next_steps'] ?? [])
->filter(static fn (mixed $step): bool => is_array($step))
->map(static function (array $step): array {
$label = is_string($step['label'] ?? null) ? trim((string) $step['label']) : '';
$url = is_string($step['url'] ?? null) ? trim((string) $step['url']) : '';
return [
'label' => $label,
'url' => $url,
];
})
->filter(static fn (array $step): bool => $step['label'] !== '' && $step['url'] !== '')
->values()
->all();
return [
'key' => is_string($check['key'] ?? null) ? trim((string) $check['key']) : '',
'title' => is_string($check['title'] ?? null) && trim((string) $check['title']) !== ''
? trim((string) $check['title'])
: 'Check',
'message' => is_string($check['message'] ?? null) && trim((string) $check['message']) !== ''
? trim((string) $check['message'])
: null,
'status' => is_string($check['status'] ?? null) ? trim((string) $check['status']) : null,
'severity' => is_string($check['severity'] ?? null) ? trim((string) $check['severity']) : null,
'reason_code' => is_string($check['reason_code'] ?? null) ? trim((string) $check['reason_code']) : null,
'blocking' => (bool) ($check['blocking'] ?? false),
'next_steps' => $nextSteps,
'acknowledgement' => is_array($acknowledgement) ? $acknowledgement : null,
];
}
/**
* @param array<int, array{label: string, checks: array<int, array<string, mixed>>, acknowledged?: bool}> $issueGroups
* @return array<int, array{label: string, placement: string, ownedByHost: bool, actionKind: string|null}>
*/
private static function nextSteps(array $issueGroups, string $placement): array
{
$steps = [];
foreach ($issueGroups as $group) {
foreach ($group['checks'] as $check) {
foreach ($check['next_steps'] ?? [] as $step) {
if (! is_array($step)) {
continue;
}
$label = is_string($step['label'] ?? null) ? trim((string) $step['label']) : '';
if ($label === '' || array_key_exists($label, $steps)) {
continue;
}
$steps[$label] = [
'label' => $label,
'placement' => $placement,
'ownedByHost' => $placement === 'host_action_zone',
'actionKind' => $placement === 'host_action_zone' ? 'assist' : 'navigation',
];
}
}
}
return array_values($steps);
}
/**
* @param array<string, mixed>|null $report
* @return array<int, string>
*/
public static function redactionNotes(?array $report): array
{
return RedactionIntegrity::verificationNotes($report);
}
}