TenantAtlas/apps/platform/app/Filament/Pages/Monitoring/EvidenceOverview.php
Ahmed Darrazi ebb68ca9bb
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m40s
feat: productize evidence and audit log disclosure
2026-05-19 23:24:00 +02:00

1047 lines
39 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Filament\Pages\Monitoring;
use App\Filament\Concerns\ClearsWorkspaceHubEnvironmentFilterState;
use App\Filament\Resources\EvidenceSnapshotResource;
use App\Filament\Resources\ReviewPackResource;
use App\Filament\Resources\StoredReportResource;
use App\Models\EnvironmentReview;
use App\Models\EvidenceSnapshot;
use App\Models\ManagedEnvironment;
use App\Models\OperationRun;
use App\Models\ReviewPack;
use App\Models\StoredReport;
use App\Models\User;
use App\Models\Workspace;
use App\Support\Badges\BadgeDomain;
use App\Support\Badges\BadgeRenderer;
use App\Support\EnvironmentReviewStatus;
use App\Support\Filament\TablePaginationProfiles;
use App\Support\Navigation\WorkspaceHubEnvironmentFilter;
use App\Support\OperationRunLinks;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceType;
use App\Support\Ui\GovernanceArtifactTruth\ArtifactTruthEnvelope;
use App\Support\Ui\GovernanceArtifactTruth\ArtifactTruthPresenter;
use App\Support\Ui\GovernanceArtifactTruth\CompressedGovernanceOutcome;
use App\Support\Ui\GovernanceArtifactTruth\SurfaceCompressionContext;
use App\Support\Workspaces\WorkspaceContext;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Pages\Page;
use Filament\Tables\Columns\TextColumn;
use Filament\Tables\Concerns\InteractsWithTable;
use Filament\Tables\Contracts\HasTable;
use Filament\Tables\Filters\SelectFilter;
use Filament\Tables\Table;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Str;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use UnitEnum;
class EvidenceOverview extends Page implements HasTable
{
use ClearsWorkspaceHubEnvironmentFilterState;
use InteractsWithTable;
protected const MONITORING_PAGE_STATE_CONTRACT = [
'surfaceKey' => 'evidence_overview',
'surfaceType' => 'simple_monitoring',
'stateFields' => [
[
'stateKey' => 'environment_id',
'stateClass' => 'contextual_prefilter',
'carrier' => 'query_param',
'queryRole' => 'durable_restorable',
'shareable' => true,
'restorableOnRefresh' => true,
'tenantSensitive' => true,
'invalidFallback' => 'discard_and_continue',
],
[
'stateKey' => 'search',
'stateClass' => 'contextual_prefilter',
'carrier' => 'query_param',
'queryRole' => 'durable_restorable',
'shareable' => true,
'restorableOnRefresh' => true,
'tenantSensitive' => false,
'invalidFallback' => 'discard_and_continue',
],
[
'stateKey' => 'tableFilters',
'stateClass' => 'shareable_restorable',
'carrier' => 'session',
'queryRole' => 'unsupported',
'shareable' => false,
'restorableOnRefresh' => true,
'tenantSensitive' => true,
'invalidFallback' => 'discard_and_continue',
],
[
'stateKey' => 'tableSort',
'stateClass' => 'shareable_restorable',
'carrier' => 'session',
'queryRole' => 'unsupported',
'shareable' => false,
'restorableOnRefresh' => true,
'tenantSensitive' => false,
'invalidFallback' => 'discard_and_continue',
],
],
'hydrationRule' => [
'precedenceOrder' => ['query', 'session', 'default'],
'appliesOnInitialMountOnly' => true,
'activeStateBecomesAuthoritativeAfterMount' => true,
'clearsOnTenantSwitch' => ['environment_id', 'managed_environment_id'],
'invalidRequestedStateFallback' => 'discard_and_continue',
],
'inspectContract' => [
'primaryModel' => 'none',
'selectedStateKey' => null,
'openedBy' => ['row_navigation'],
'presentation' => 'navigate_to_canonical_detail',
'shareable' => false,
'invalidSelectionFallback' => 'discard_and_continue',
],
'shareableStateKeys' => ['environment_id', 'search'],
'localOnlyStateKeys' => [],
];
protected static bool $isDiscovered = false;
protected static bool $shouldRegisterNavigation = false;
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-shield-check';
protected static string|UnitEnum|null $navigationGroup = 'Monitoring';
protected static ?string $title = 'Evidence Overview';
protected string $view = 'filament.pages.monitoring.evidence-overview';
/**
* @var list<array<string, mixed>>
*/
public array $rows = [];
/**
* @var array<int, ManagedEnvironment>|null
*/
private ?array $accessibleTenants = null;
private ?Collection $cachedSnapshots = null;
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
{
return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly, ActionSurfaceType::ReadOnlyRegistryReport)
->satisfy(ActionSurfaceSlot::ListHeader, 'The overview header exposes a clear-filters action when a tenant prefilter is active.')
->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'The overview exposes a single drill-down link per row without a More menu.')
->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The overview does not expose bulk actions.')
->satisfy(ActionSurfaceSlot::ListEmptyState, 'The empty state explains the current scope and offers a clear-filters CTA.');
}
/**
* @return array<string, mixed>
*/
public static function monitoringPageStateContract(): array
{
return self::MONITORING_PAGE_STATE_CONTRACT;
}
public function mount(): void
{
$this->authorizeWorkspaceAccess();
$this->resetWorkspaceHubEnvironmentFilterStateForCleanEntry(request());
$this->seedTableStateFromQuery();
$this->mountInteractsWithTable();
$this->resetWorkspaceHubEnvironmentFilterStateForCleanEntry(request());
$this->rows = $this->rowsForState($this->tableFilters ?? [], $this->tableSearch)->values()->all();
}
public function table(Table $table): Table
{
return $table
->defaultSort('tenant_name')
->defaultPaginationPageOption(25)
->paginated(TablePaginationProfiles::customPage())
->persistFiltersInSession()
->persistSearchInSession()
->persistSortInSession()
->searchable()
->searchPlaceholder('Search evidence or next step')
->records(function (
?string $sortColumn,
?string $sortDirection,
?string $search,
array $filters,
int $page,
int $recordsPerPage
): LengthAwarePaginator {
$rows = $this->rowsForState($filters, $search);
$rows = $this->sortRows($rows, $sortColumn, $sortDirection);
return $this->paginateRows($rows, $page, $recordsPerPage);
})
->filters([
SelectFilter::make('managed_environment_id')
->label('Environment')
->options(fn (): array => $this->tenantFilterOptions())
->searchable(),
])
->columns([
TextColumn::make('tenant_name')
->label('Environment')
->sortable(),
TextColumn::make('artifact_truth_label')
->label('Outcome')
->badge()
->color(fn (array $record): string => (string) ($record['artifact_truth_color'] ?? 'gray'))
->icon(fn (array $record): ?string => is_string($record['artifact_truth_icon'] ?? null) ? $record['artifact_truth_icon'] : null)
->description(fn (array $record): ?string => is_string($record['artifact_truth_explanation'] ?? null) ? $record['artifact_truth_explanation'] : null)
->sortable()
->wrap(),
TextColumn::make('generated_at')
->label('Generated')
->placeholder('—')
->sortable(),
TextColumn::make('next_step')
->label('Next step')
->wrap(),
])
->recordUrl(fn ($record): ?string => is_array($record) ? (is_string($record['view_url'] ?? null) ? $record['view_url'] : null) : null)
->actions([])
->bulkActions([])
->emptyStateHeading('No evidence snapshots in this scope')
->emptyStateDescription(fn (): string => $this->hasActiveOverviewFilters()
? 'Clear the current filters to return to the full workspace evidence overview.'
: 'Adjust filters or create an environment snapshot to populate the workspace overview.')
->emptyStateActions([
Action::make('clear_filters')
->label('Clear filters')
->icon('heroicon-o-x-mark')
->color('gray')
->visible(fn (): bool => $this->hasActiveOverviewFilters())
->action(fn (): mixed => $this->clearOverviewFilters()),
]);
}
/**
* @return array<Action>
*/
protected function getHeaderActions(): array
{
return [
Action::make('clear_filters')
->label('Clear filters')
->color('gray')
->visible(fn (): bool => $this->hasActiveOverviewFilters())
->action(fn (): mixed => $this->clearOverviewFilters()),
];
}
public function clearOverviewFilters(): void
{
$this->tableFilters = [
'managed_environment_id' => ['value' => null],
];
$this->tableDeferredFilters = $this->tableFilters;
$this->tableSearch = '';
$this->rows = $this->rowsForState($this->tableFilters, $this->tableSearch)->values()->all();
session()->forget($this->getTableFiltersSessionKey());
session()->put($this->getTableSearchSessionKey(), $this->tableSearch);
$this->clearWorkspaceHubEnvironmentFilterState(request());
$this->redirectToCleanWorkspaceHubUrl($this->overviewUrl(), request());
}
/**
* @return array{label: string, clear_url: string}|null
*/
public function environmentFilterChip(): ?array
{
$tenant = $this->filteredTenant();
if (! $tenant instanceof ManagedEnvironment) {
return null;
}
return [
'label' => (string) $tenant->name,
'clear_url' => $this->cleanWorkspaceHubUrl($this->overviewUrl()),
];
}
/**
* @return array<string, mixed>
*/
public function evidenceDisclosurePayload(): array
{
$snapshots = $this->scopedSnapshots();
$primarySnapshot = $snapshots->first();
$primaryTenant = $primarySnapshot?->tenant;
$primaryReviewPack = $primarySnapshot instanceof EvidenceSnapshot
? $this->latestReviewPackForSnapshot($primarySnapshot)
: null;
$primaryStoredReport = $primaryTenant instanceof ManagedEnvironment
? $this->latestStoredReportForTenant($primaryTenant)
: null;
$primaryOperationRun = $this->primaryOperationRun($primarySnapshot, $primaryReviewPack);
$primaryAction = $this->primaryEvidenceAction($primarySnapshot, $primaryReviewPack, $primaryStoredReport, $primaryOperationRun);
$primaryState = $this->primaryProofState($primarySnapshot, $primaryReviewPack, $primaryStoredReport, $primaryOperationRun);
return [
'scope_label' => $this->evidenceScopeLabel(),
'scope_description' => $this->evidenceScopeDescription($snapshots),
'snapshot_count' => $snapshots->count(),
'primary_title' => $primaryTenant instanceof ManagedEnvironment
? $primaryTenant->name
: 'No evidence for this scope',
'primary_summary' => $primarySnapshot instanceof EvidenceSnapshot
? $this->productSafeEvidenceReason($this->snapshotOutcome($primarySnapshot)->primaryReason)
: 'No evidence snapshot has been generated for the active workspace scope.',
'primary_state' => $primaryState,
'primary_action' => $primaryAction,
'cards' => [
$this->snapshotProofCard($primarySnapshot),
$this->reviewPackProofCard($primaryReviewPack, $primarySnapshot),
$this->storedReportProofCard($primaryStoredReport, $primaryTenant),
$this->operationProofCard($primaryOperationRun),
],
'path_items' => [
$this->snapshotPathItem($primarySnapshot),
$this->reviewPackPathItem($primaryReviewPack, $primarySnapshot),
$this->storedReportPathItem($primaryStoredReport, $primaryTenant),
$this->operationPathItem($primaryOperationRun),
],
];
}
/**
* @return Collection<int, EvidenceSnapshot>
*/
private function scopedSnapshots(): Collection
{
$snapshotIds = $this->rowsForState($this->tableFilters ?? [], $this->tableSearch)
->pluck('snapshot_id')
->map(static fn (mixed $snapshotId): int => (int) $snapshotId)
->all();
if ($snapshotIds === []) {
return collect();
}
return $this->latestAccessibleSnapshots()
->filter(static fn (EvidenceSnapshot $snapshot): bool => in_array((int) $snapshot->getKey(), $snapshotIds, true))
->values();
}
/**
* @param Collection<int, EvidenceSnapshot> $snapshots
*/
private function evidenceScopeDescription(Collection $snapshots): string
{
$tenant = $this->filteredTenant();
if ($tenant instanceof ManagedEnvironment) {
return 'Filtered to '.$tenant->name.'. Proof states below are derived from records directly attributed to this environment.';
}
if ($snapshots->isEmpty()) {
return 'Workspace-wide proof view. No accessible evidence snapshot currently matches the active search or filters.';
}
return sprintf(
'Workspace-wide proof view across %d accessible environment%s.',
$snapshots->count(),
$snapshots->count() === 1 ? '' : 's',
);
}
private function evidenceScopeLabel(): string
{
$tenant = $this->filteredTenant();
return $tenant instanceof ManagedEnvironment
? 'Environment proof scope'
: 'Workspace proof scope';
}
private function latestReviewPackForSnapshot(EvidenceSnapshot $snapshot): ?ReviewPack
{
$reviewPack = $snapshot->reviewPacks
->sortByDesc(fn (ReviewPack $pack): int => $pack->generated_at?->getTimestamp() ?? 0)
->first();
return $reviewPack instanceof ReviewPack ? $reviewPack : null;
}
private function latestStoredReportForTenant(ManagedEnvironment $tenant): ?StoredReport
{
$user = auth()->user();
if (! $user instanceof User) {
return null;
}
$visibleReportTypes = collect(StoredReportResource::supportedReportTypes())
->filter(function (string $reportType) use ($tenant, $user): bool {
$capability = StoredReportResource::capabilityForReportType($reportType);
return is_string($capability) && $user->can($capability, $tenant);
})
->values()
->all();
if ($visibleReportTypes === []) {
return null;
}
return StoredReport::query()
->where('workspace_id', (int) $tenant->workspace_id)
->where('managed_environment_id', (int) $tenant->getKey())
->whereIn('report_type', $visibleReportTypes)
->latest('created_at')
->first();
}
private function primaryOperationRun(?EvidenceSnapshot $snapshot, ?ReviewPack $reviewPack): ?OperationRun
{
$run = $snapshot?->operationRun;
if ($run instanceof OperationRun && $this->canViewOperationRun($run)) {
return $run;
}
$run = $reviewPack?->operationRun;
return $run instanceof OperationRun && $this->canViewOperationRun($run)
? $run
: null;
}
private function canViewOperationRun(OperationRun $run): bool
{
$user = auth()->user();
return $user instanceof User && Gate::forUser($user)->allows('view', $run);
}
/**
* @return array{label:string,url:string}|null
*/
private function primaryEvidenceAction(?EvidenceSnapshot $snapshot, ?ReviewPack $reviewPack, ?StoredReport $storedReport, ?OperationRun $operationRun): ?array
{
if ($snapshot instanceof EvidenceSnapshot && $snapshot->tenant instanceof ManagedEnvironment) {
return [
'label' => 'Open evidence snapshot',
'url' => EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $snapshot->tenant, panel: 'admin'),
];
}
if ($reviewPack instanceof ReviewPack && $reviewPack->tenant instanceof ManagedEnvironment) {
return [
'label' => 'Open review pack',
'url' => ReviewPackResource::getUrl('view', ['record' => $reviewPack], tenant: $reviewPack->tenant, panel: 'admin'),
];
}
if ($storedReport instanceof StoredReport && $storedReport->tenant instanceof ManagedEnvironment) {
return [
'label' => 'Open stored report',
'url' => StoredReportResource::getUrl('view', ['record' => $storedReport], tenant: $storedReport->tenant, panel: 'admin'),
];
}
if ($operationRun instanceof OperationRun) {
return [
'label' => OperationRunLinks::openLabel(),
'url' => OperationRunLinks::tenantlessView($operationRun),
];
}
return null;
}
/**
* @return array<string, mixed>
*/
private function snapshotProofCard(?EvidenceSnapshot $snapshot): array
{
if (! $snapshot instanceof EvidenceSnapshot) {
return $this->unavailableProofCard(
'Evidence snapshot',
'Not generated',
'No active evidence snapshot is available in this scope.',
'gray',
);
}
$outcome = $this->snapshotOutcome($snapshot);
$isEmptySnapshot = $this->isEmptyEvidenceSnapshot($snapshot);
return [
'label' => 'Evidence snapshot',
'value' => $isEmptySnapshot ? 'Proof incomplete' : $outcome->primaryLabel,
'path_state' => $isEmptySnapshot ? 'Empty' : $outcome->primaryLabel,
'description' => $isEmptySnapshot
? 'A proof record exists, but no usable captured evidence is available yet.'
: $this->productSafeEvidenceReason($outcome->primaryReason),
'color' => $outcome->primaryBadge->color,
'url' => $snapshot->tenant instanceof ManagedEnvironment
? EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $snapshot->tenant, panel: 'admin')
: null,
'meta' => $snapshot->generated_at?->diffForHumans() ?? 'Freshness unavailable',
];
}
/**
* @return array<string, mixed>
*/
private function reviewPackProofCard(?ReviewPack $reviewPack, ?EvidenceSnapshot $snapshot): array
{
if (! $reviewPack instanceof ReviewPack) {
return $this->unavailableProofCard(
'Review pack',
$snapshot instanceof EvidenceSnapshot ? 'Not generated' : 'Not applicable',
$snapshot instanceof EvidenceSnapshot
? 'No review pack has been generated from the current evidence snapshot.'
: 'A review pack requires an evidence snapshot first.',
'gray',
);
}
return [
'label' => 'Review pack',
'value' => BadgeRenderer::label(BadgeDomain::ReviewPackStatus)((string) $reviewPack->status),
'description' => $reviewPack->isReady()
? 'Customer-review artifact exists for this evidence path.'
: 'Review pack exists but is not ready for sharing.',
'color' => BadgeRenderer::color(BadgeDomain::ReviewPackStatus)((string) $reviewPack->status),
'url' => $reviewPack->tenant instanceof ManagedEnvironment
? ReviewPackResource::getUrl('view', ['record' => $reviewPack], tenant: $reviewPack->tenant, panel: 'admin')
: null,
'meta' => $reviewPack->generated_at?->diffForHumans() ?? 'Generated time unavailable',
];
}
/**
* @return array<string, mixed>
*/
private function storedReportProofCard(?StoredReport $storedReport, ?ManagedEnvironment $tenant): array
{
if (! $tenant instanceof ManagedEnvironment) {
return $this->unavailableProofCard(
'Stored report / export',
'Not applicable',
'Stored report availability is evaluated after an evidence scope exists.',
'gray',
);
}
if (! $storedReport instanceof StoredReport) {
return $this->unavailableProofCard(
'Stored report / export',
'Unavailable',
'No repo-supported stored report is available for this environment scope.',
'gray',
);
}
return [
'label' => 'Stored report / export',
'value' => 'Available',
'description' => StoredReportResource::reportFamilyReportLabel((string) $storedReport->report_type),
'color' => 'success',
'url' => StoredReportResource::getUrl('view', ['record' => $storedReport], tenant: $tenant, panel: 'admin'),
'meta' => $storedReport->created_at?->diffForHumans() ?? 'Report time unavailable',
];
}
/**
* @return array<string, mixed>
*/
private function operationProofCard(?OperationRun $operationRun): array
{
if (! $operationRun instanceof OperationRun) {
return $this->unavailableProofCard(
'Operation proof',
'Unavailable',
'No authorized operation run is linked to the current proof path.',
'gray',
);
}
return [
'label' => 'Operation proof',
'value' => 'Available',
'description' => OperationRunLinks::identifier($operationRun),
'color' => 'info',
'url' => OperationRunLinks::tenantlessView($operationRun),
'meta' => $operationRun->completed_at?->diffForHumans() ?? $operationRun->created_at?->diffForHumans() ?? 'Run time unavailable',
];
}
/**
* @return array{label:string,value:string,description:string,color:string,url:null,meta:string}
*/
private function unavailableProofCard(string $label, string $value, string $description, string $color): array
{
return [
'label' => $label,
'value' => $value,
'description' => $description,
'color' => $color,
'url' => null,
'meta' => 'Derived from current repo truth',
];
}
/**
* @return array<string, mixed>
*/
private function snapshotPathItem(?EvidenceSnapshot $snapshot): array
{
return $this->pathItemFromCard($this->snapshotProofCard($snapshot));
}
/**
* @return array<string, mixed>
*/
private function reviewPackPathItem(?ReviewPack $reviewPack, ?EvidenceSnapshot $snapshot): array
{
return $this->pathItemFromCard($this->reviewPackProofCard($reviewPack, $snapshot));
}
/**
* @return array<string, mixed>
*/
private function storedReportPathItem(?StoredReport $storedReport, ?ManagedEnvironment $tenant): array
{
return $this->pathItemFromCard($this->storedReportProofCard($storedReport, $tenant));
}
/**
* @return array<string, mixed>
*/
private function operationPathItem(?OperationRun $operationRun): array
{
return $this->pathItemFromCard($this->operationProofCard($operationRun));
}
/**
* @param array<string, mixed> $card
* @return array<string, mixed>
*/
private function pathItemFromCard(array $card): array
{
return [
'label' => (string) $card['label'],
'state' => (string) ($card['path_state'] ?? $card['value']),
'description' => (string) $card['description'],
'color' => (string) $card['color'],
'url' => is_string($card['url'] ?? null) ? $card['url'] : null,
];
}
/**
* @return array{label:string,color:string,reason:string,impact:string}|null
*/
private function primaryProofState(
?EvidenceSnapshot $snapshot,
?ReviewPack $reviewPack,
?StoredReport $storedReport,
?OperationRun $operationRun,
): ?array {
if (! $snapshot instanceof EvidenceSnapshot || ! $this->isEmptyEvidenceSnapshot($snapshot)) {
return null;
}
return [
'label' => 'Proof incomplete',
'color' => 'warning',
'reason' => 'Primary evidence snapshot is empty.',
'impact' => $this->emptySnapshotImpact($reviewPack, $storedReport, $operationRun),
];
}
private function emptySnapshotImpact(?ReviewPack $reviewPack, ?StoredReport $storedReport, ?OperationRun $operationRun): string
{
if ($reviewPack instanceof ReviewPack && $storedReport instanceof StoredReport && $operationRun instanceof OperationRun) {
return 'Supporting proof exists through the review pack, stored report, and operation record.';
}
return 'Supporting proof is limited; use the available evidence path items before relying on this snapshot.';
}
private function isEmptyEvidenceSnapshot(EvidenceSnapshot $snapshot): bool
{
return $this->snapshotTruth($snapshot)->contentState === 'empty';
}
private function productSafeEvidenceReason(string $reason): string
{
return $reason === 'The artifact row exists, but it does not contain usable captured content.'
? 'A proof record exists, but no usable captured evidence is available yet.'
: $reason;
}
private function snapshotTruth(EvidenceSnapshot $snapshot, bool $fresh = false): ArtifactTruthEnvelope
{
$presenter = app(ArtifactTruthPresenter::class);
return $fresh
? $presenter->forEvidenceSnapshotFresh($snapshot)
: $presenter->forEvidenceSnapshot($snapshot);
}
private function snapshotOutcome(EvidenceSnapshot $snapshot, bool $fresh = false): CompressedGovernanceOutcome
{
$presenter = app(ArtifactTruthPresenter::class);
return $presenter->compressedOutcomeFor($snapshot, SurfaceCompressionContext::evidenceOverview(), $fresh)
?? $presenter->compressedOutcomeFromEnvelope(
$this->snapshotTruth($snapshot, $fresh),
SurfaceCompressionContext::evidenceOverview(),
);
}
private function authorizeWorkspaceAccess(): void
{
$user = auth()->user();
if (! $user instanceof User) {
throw new AuthenticationException;
}
app(WorkspaceContext::class)->currentWorkspaceForMemberOrFail($user, request());
}
/**
* @return array<int, ManagedEnvironment>
*/
private function accessibleTenants(): array
{
if (is_array($this->accessibleTenants)) {
return $this->accessibleTenants;
}
$user = auth()->user();
if (! $user instanceof User) {
return $this->accessibleTenants = [];
}
$workspaceId = $this->workspaceId();
return $this->accessibleTenants = $user->accessibleManagedEnvironmentsQuery($workspaceId)
->orderBy('managed_environments.name')
->get()
->filter(fn (ManagedEnvironment $tenant): bool => (int) $tenant->workspace_id === $workspaceId && $user->can('evidence.view', $tenant))
->values()
->all();
}
/**
* @return array<string, string>
*/
private function tenantFilterOptions(): array
{
return collect($this->accessibleTenants())
->mapWithKeys(static fn (ManagedEnvironment $tenant): array => [
(string) $tenant->getKey() => $tenant->name,
])
->all();
}
/**
* @param array<string, mixed> $filters
* @return Collection<string, array<string, mixed>>
*/
private function rowsForState(array $filters = [], ?string $search = null): Collection
{
$rows = $this->baseRows();
$tenantFilter = $this->normalizeTenantFilter($filters['managed_environment_id']['value'] ?? data_get($this->tableFilters, 'managed_environment_id.value'));
$normalizedSearch = Str::lower(trim((string) ($search ?? $this->tableSearch)));
if ($tenantFilter !== null) {
$rows = $rows->where('managed_environment_id', $tenantFilter);
}
if ($normalizedSearch === '') {
return $rows;
}
return $rows->filter(function (array $row) use ($normalizedSearch): bool {
$haystack = implode(' ', [
(string) ($row['tenant_name'] ?? ''),
(string) ($row['artifact_truth_label'] ?? ''),
(string) ($row['artifact_truth_explanation'] ?? ''),
(string) ($row['freshness_label'] ?? ''),
(string) ($row['next_step'] ?? ''),
]);
return str_contains(Str::lower($haystack), $normalizedSearch);
});
}
/**
* @return Collection<string, array<string, mixed>>
*/
private function baseRows(): Collection
{
$snapshots = $this->latestAccessibleSnapshots();
$currentReviewTenantIds = $this->currentReviewTenantIds($snapshots);
return $snapshots->mapWithKeys(function (EvidenceSnapshot $snapshot) use ($currentReviewTenantIds): array {
return [(string) $snapshot->getKey() => $this->rowForSnapshot($snapshot, $currentReviewTenantIds)];
});
}
/**
* @return Collection<int, EvidenceSnapshot>
*/
private function latestAccessibleSnapshots(): Collection
{
if ($this->cachedSnapshots instanceof Collection) {
return $this->cachedSnapshots;
}
$tenantIds = collect($this->accessibleTenants())
->map(static fn (ManagedEnvironment $tenant): int => (int) $tenant->getKey())
->all();
$query = EvidenceSnapshot::query()
->with([
'tenant',
'operationRun',
'reviewPacks.operationRun',
'items',
])
->where('workspace_id', $this->workspaceId())
->where('status', 'active')
->latest('generated_at');
if ($tenantIds === []) {
$query->whereRaw('1 = 0');
} else {
$query->whereIn('managed_environment_id', $tenantIds);
}
return $this->cachedSnapshots = $query->get()->unique('managed_environment_id')->values();
}
/**
* @param Collection<int, EvidenceSnapshot> $snapshots
* @return array<int, bool>
*/
private function currentReviewTenantIds(Collection $snapshots): array
{
return EnvironmentReview::query()
->where('workspace_id', $this->workspaceId())
->whereIn('managed_environment_id', $snapshots->pluck('managed_environment_id')->map(static fn (mixed $tenantId): int => (int) $tenantId)->all())
->whereIn('status', [
EnvironmentReviewStatus::Draft->value,
EnvironmentReviewStatus::Ready->value,
EnvironmentReviewStatus::Published->value,
])
->pluck('managed_environment_id')
->mapWithKeys(static fn (mixed $tenantId): array => [(int) $tenantId => true])
->all();
}
/**
* @param array<int, bool> $currentReviewTenantIds
* @return array<string, mixed>
*/
private function rowForSnapshot(EvidenceSnapshot $snapshot, array $currentReviewTenantIds): array
{
$truth = $this->snapshotTruth($snapshot);
$outcome = $this->snapshotOutcome($snapshot);
$tenantId = (int) $snapshot->managed_environment_id;
$hasCurrentReview = $currentReviewTenantIds[$tenantId] ?? false;
$nextStep = ! $hasCurrentReview && $truth->contentState === 'trusted' && $truth->freshnessState === 'current'
? 'Create a current review from this evidence snapshot'
: $outcome->nextActionText;
return [
'tenant_name' => $snapshot->tenant?->name ?? 'Unknown tenant',
'managed_environment_id' => $tenantId,
'snapshot_id' => (int) $snapshot->getKey(),
'generated_at' => $snapshot->generated_at?->toDateTimeString(),
'artifact_truth_label' => $truth->contentState === 'empty' ? 'Proof incomplete' : $outcome->primaryLabel,
'artifact_truth_color' => $outcome->primaryBadge->color,
'artifact_truth_icon' => $outcome->primaryBadge->icon,
'artifact_truth_explanation' => $this->productSafeEvidenceReason($outcome->primaryReason),
'artifact_truth' => [
'label' => $truth->contentState === 'empty' ? 'Proof incomplete' : $outcome->primaryLabel,
'color' => $outcome->primaryBadge->color,
'icon' => $outcome->primaryBadge->icon,
'explanation' => $this->productSafeEvidenceReason($outcome->primaryReason),
],
'next_step' => $nextStep,
'view_url' => $snapshot->tenant
? EvidenceSnapshotResource::getUrl('view', ['record' => $snapshot], tenant: $snapshot->tenant)
: null,
];
}
/**
* @param Collection<string, array<string, mixed>> $rows
* @return Collection<string, array<string, mixed>>
*/
private function sortRows(Collection $rows, ?string $sortColumn, ?string $sortDirection): Collection
{
$sortColumn = in_array($sortColumn, ['tenant_name', 'artifact_truth_label', 'generated_at'], true)
? $sortColumn
: 'tenant_name';
$descending = Str::lower((string) ($sortDirection ?? 'asc')) === 'desc';
$records = $rows->all();
uasort($records, static function (array $left, array $right) use ($sortColumn, $descending): int {
$comparison = strnatcasecmp((string) ($left[$sortColumn] ?? ''), (string) ($right[$sortColumn] ?? ''));
if ($comparison === 0) {
$comparison = strnatcasecmp((string) ($left['tenant_name'] ?? ''), (string) ($right['tenant_name'] ?? ''));
}
return $descending ? ($comparison * -1) : $comparison;
});
return collect($records);
}
/**
* @param Collection<string, array<string, mixed>> $rows
*/
private function paginateRows(Collection $rows, int $page, int $recordsPerPage): LengthAwarePaginator
{
return new LengthAwarePaginator(
items: $rows->forPage($page, $recordsPerPage),
total: $rows->count(),
perPage: $recordsPerPage,
currentPage: $page,
);
}
private function seedTableStateFromQuery(): void
{
$query = request()->query();
if (array_key_exists('search', $query)) {
$this->tableSearch = trim((string) request()->query('search', ''));
}
if (! array_key_exists('environment_id', $query)) {
return;
}
$workspace = $this->workspace();
if (! $workspace instanceof Workspace) {
return;
}
$filter = WorkspaceHubEnvironmentFilter::fromRequest(request(), $workspace);
if (! $filter instanceof WorkspaceHubEnvironmentFilter) {
return;
}
$tenantFilter = $this->normalizeTenantFilter($filter->environmentId());
if ($tenantFilter === null) {
throw new NotFoundHttpException;
}
$this->tableFilters = [
'managed_environment_id' => ['value' => (string) $tenantFilter],
];
$this->tableDeferredFilters = $this->tableFilters;
}
private function normalizeTenantFilter(mixed $value): ?int
{
if (! is_numeric($value)) {
return null;
}
$requestedTenantId = (int) $value;
$allowedTenantIds = collect($this->accessibleTenants())
->map(static fn (ManagedEnvironment $tenant): int => (int) $tenant->getKey())
->all();
return in_array($requestedTenantId, $allowedTenantIds, true)
? $requestedTenantId
: null;
}
private function hasActiveOverviewFilters(): bool
{
return filled(data_get($this->tableFilters, 'managed_environment_id.value'))
|| trim((string) $this->tableSearch) !== '';
}
private function overviewUrl(array $overrides = []): string
{
return route(
'admin.evidence.overview',
array_filter($overrides, static fn (mixed $value): bool => $value !== null && $value !== '' && $value !== []),
);
}
private function workspaceId(): int
{
$user = auth()->user();
if (! $user instanceof User) {
throw new AuthenticationException;
}
return (int) app(WorkspaceContext::class)
->currentWorkspaceForMemberOrFail($user, request())
->getKey();
}
private function workspace(): ?Workspace
{
$user = auth()->user();
if (! $user instanceof User) {
throw new AuthenticationException;
}
return app(WorkspaceContext::class)->currentWorkspaceForMemberOrFail($user, request());
}
private function filteredTenant(): ?ManagedEnvironment
{
$tenantId = $this->normalizeTenantFilter(data_get($this->tableFilters, 'managed_environment_id.value'));
if (! is_int($tenantId)) {
return null;
}
foreach ($this->accessibleTenants() as $tenant) {
if ((int) $tenant->getKey() === $tenantId) {
return $tenant;
}
}
return null;
}
}