667 lines
23 KiB
PHP
667 lines
23 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Filament\Pages\Governance;
|
|
|
|
use App\Filament\Concerns\CleansAdminTenantQueryParameter;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\User;
|
|
use App\Models\Workspace;
|
|
use App\Services\Auth\CapabilityResolver;
|
|
use App\Services\Auth\WorkspaceCapabilityResolver;
|
|
use App\Services\EnvironmentReviews\EnvironmentReviewRegisterService;
|
|
use App\Support\Auth\Capabilities;
|
|
use App\Support\GovernanceInbox\GovernanceInboxSectionBuilder;
|
|
use App\Support\Navigation\CanonicalNavigationContext;
|
|
use App\Support\Navigation\WorkspaceHubEnvironmentFilter;
|
|
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\Workspaces\WorkspaceContext;
|
|
use BackedEnum;
|
|
use Filament\Facades\Filament;
|
|
use Filament\Pages\Page;
|
|
use Illuminate\Support\Str;
|
|
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
|
use UnitEnum;
|
|
|
|
class GovernanceInbox extends Page
|
|
{
|
|
use CleansAdminTenantQueryParameter;
|
|
|
|
protected static bool $isDiscovered = false;
|
|
|
|
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-inbox-stack';
|
|
|
|
protected static string|UnitEnum|null $navigationGroup = 'Governance';
|
|
|
|
protected static ?string $navigationLabel = 'Governance inbox';
|
|
|
|
protected static ?int $navigationSort = 5;
|
|
|
|
protected static ?string $title = 'Governance Inbox';
|
|
|
|
protected static ?string $slug = 'governance/inbox';
|
|
|
|
protected string $view = 'filament.pages.governance.governance-inbox';
|
|
|
|
public function getSubheading(): ?string
|
|
{
|
|
return 'Prioritized governance decisions, owners, evidence, and follow-up actions across entitled environments.';
|
|
}
|
|
|
|
/**
|
|
* @var array<int, ManagedEnvironment>|null
|
|
*/
|
|
private ?array $authorizedTenants = null;
|
|
|
|
/**
|
|
* @var array<int, ManagedEnvironment>|null
|
|
*/
|
|
private ?array $visibleFindingTenants = null;
|
|
|
|
/**
|
|
* @var array<int, ManagedEnvironment>|null
|
|
*/
|
|
private ?array $reviewTenants = null;
|
|
|
|
/**
|
|
* @var array<string, mixed>|null
|
|
*/
|
|
private ?array $inboxPayload = null;
|
|
|
|
/**
|
|
* @var array<string, mixed>|null
|
|
*/
|
|
private ?array $unfilteredInboxPayload = null;
|
|
|
|
/**
|
|
* @var array<string, mixed>|null
|
|
*/
|
|
private ?array $decisionWorkbench = null;
|
|
|
|
private ?Workspace $workspace = null;
|
|
|
|
private ?bool $visibleAlertsFamily = null;
|
|
|
|
private ?bool $visibleFindingExceptionsFamily = null;
|
|
|
|
public ?int $tenantId = null;
|
|
|
|
public ?string $family = null;
|
|
|
|
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
|
|
{
|
|
return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly, ActionSurfaceType::ReadOnlyRegistryReport)
|
|
->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions keep the workspace decision surface calm and read-only.')
|
|
->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
|
|
->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'The governance inbox routes into existing source surfaces instead of exposing row-level secondary actions.')
|
|
->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The governance inbox does not expose bulk actions.')
|
|
->satisfy(ActionSurfaceSlot::ListEmptyState, 'Empty states stay calm and capability-safe when no visible attention exists.')
|
|
->exempt(ActionSurfaceSlot::DetailHeader, 'The governance inbox owns no local detail surface in v1.');
|
|
}
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->authorizeWorkspaceMembership();
|
|
$this->applyRequestedTenantPrefilter();
|
|
$this->family = $this->resolveRequestedFamily();
|
|
$this->ensureAtLeastOneVisibleFamily();
|
|
$this->ensureRequestedFamilyIsVisible();
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function appliedScope(): array
|
|
{
|
|
$selectedTenant = $this->selectedTenant();
|
|
$availableFamilies = collect($this->availableFamilies())
|
|
->keyBy('key');
|
|
|
|
return [
|
|
'workspace_label' => $this->workspace()?->name,
|
|
'tenant_label' => $selectedTenant?->name,
|
|
'tenant_prefilter_source' => $selectedTenant instanceof ManagedEnvironment ? 'explicit_filter' : 'none',
|
|
'family_key' => $this->family,
|
|
'family_label' => $this->family !== null
|
|
? ($availableFamilies->get($this->family)['label'] ?? Str::headline($this->family))
|
|
: 'All attention',
|
|
'total_count' => (int) ($this->inboxPayload()['total_count'] ?? 0),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return list<array{key: string, label: string, count: int}>
|
|
*/
|
|
public function availableFamilies(): array
|
|
{
|
|
return $this->inboxPayload()['available_families'] ?? [];
|
|
}
|
|
|
|
/**
|
|
* @return list<array<string, mixed>>
|
|
*/
|
|
public function sections(): array
|
|
{
|
|
return $this->inboxPayload()['sections'] ?? [];
|
|
}
|
|
|
|
/**
|
|
* @return array{
|
|
* question: string,
|
|
* selected_item: array<string, mixed>|null,
|
|
* summary_cards: list<array{label: string, value: string, description: string}>,
|
|
* diagnostics: array{label: string, state: string, body: string}
|
|
* }
|
|
*/
|
|
public function decisionWorkbench(): array
|
|
{
|
|
if (is_array($this->decisionWorkbench)) {
|
|
return $this->decisionWorkbench;
|
|
}
|
|
|
|
$entries = $this->workbenchEntries();
|
|
$selectedItem = $entries
|
|
->sortBy([
|
|
fn (array $entry): int => (int) ($entry['urgency_rank'] ?? 999),
|
|
fn (array $entry): string => (string) ($entry['headline'] ?? ''),
|
|
])
|
|
->first();
|
|
|
|
return $this->decisionWorkbench = [
|
|
'question' => 'What decision clears the highest-priority item?',
|
|
'selected_item' => is_array($selectedItem) ? $this->normalizeWorkbenchItem($selectedItem) : null,
|
|
'summary_cards' => $this->summaryCards($entries),
|
|
'diagnostics' => [
|
|
'label' => 'Diagnostics',
|
|
'state' => 'Collapsed',
|
|
'body' => 'Source diagnostics and raw support details stay on authorized source surfaces. This workbench shows decision, evidence, and proof state only.',
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function calmEmptyState(): array
|
|
{
|
|
if ($this->tenantFilterAloneExcludesRows()) {
|
|
return [
|
|
'title' => 'This environment filter is hiding other visible attention',
|
|
'body' => 'The current environment scope is calm, but other visible environments in this workspace still have governance attention.',
|
|
'action_label' => 'Clear environment filter',
|
|
'action_url' => $this->pageUrl(['environment_id' => null, 'family' => null]),
|
|
];
|
|
}
|
|
|
|
return [
|
|
'title' => 'No governance decisions need attention',
|
|
'body' => 'The current workspace scope has no repo-backed governance decisions requiring action.',
|
|
'action_label' => null,
|
|
'action_url' => null,
|
|
];
|
|
}
|
|
|
|
public function hasTenantPrefilter(): bool
|
|
{
|
|
return $this->selectedTenant() instanceof ManagedEnvironment;
|
|
}
|
|
|
|
public function isActiveFamily(?string $familyKey): bool
|
|
{
|
|
return $this->family === $familyKey;
|
|
}
|
|
|
|
public function pageUrl(array $overrides = []): string
|
|
{
|
|
$selectedTenant = $this->selectedTenant();
|
|
$resolvedTenant = array_key_exists('environment_id', $overrides)
|
|
? $overrides['environment_id']
|
|
: ($selectedTenant?->getKey() !== null ? (string) $selectedTenant->getKey() : null);
|
|
$resolvedFamily = array_key_exists('family', $overrides)
|
|
? $overrides['family']
|
|
: $this->family;
|
|
|
|
return static::getUrl(
|
|
panel: 'admin',
|
|
parameters: array_filter([
|
|
'environment_id' => (is_string($resolvedTenant) || is_numeric($resolvedTenant)) && (string) $resolvedTenant !== '' ? (string) $resolvedTenant : null,
|
|
'family' => is_string($resolvedFamily) && $resolvedFamily !== '' ? $resolvedFamily : null,
|
|
], static fn (mixed $value): bool => $value !== null && $value !== ''),
|
|
);
|
|
}
|
|
|
|
public function navigationContext(): CanonicalNavigationContext
|
|
{
|
|
return CanonicalNavigationContext::forGovernanceInbox(
|
|
canonicalRouteName: static::getRouteName(Filament::getPanel('admin')),
|
|
tenantId: $this->tenantId,
|
|
backLinkUrl: $this->pageUrl(),
|
|
familyKey: $this->family,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return \Illuminate\Support\Collection<int, array<string, mixed>>
|
|
*/
|
|
private function workbenchEntries(): \Illuminate\Support\Collection
|
|
{
|
|
return collect($this->sections())
|
|
->flatMap(function (array $section): array {
|
|
$entries = is_array($section['entries'] ?? null) ? $section['entries'] : [];
|
|
|
|
return array_map(function (array $entry) use ($section): array {
|
|
$entry['section_key'] = (string) ($section['key'] ?? $entry['family_key'] ?? 'governance');
|
|
$entry['section_label'] = (string) ($section['label'] ?? 'Governance item');
|
|
|
|
return $entry;
|
|
}, $entries);
|
|
})
|
|
->values();
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $item
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function normalizeWorkbenchItem(array $item): array
|
|
{
|
|
return [
|
|
'section_label' => (string) ($item['section_label'] ?? 'Governance item'),
|
|
'environment_label' => filled($item['tenant_label'] ?? null) ? (string) $item['tenant_label'] : 'Workspace-wide',
|
|
'title' => (string) ($item['headline'] ?? 'Governance item'),
|
|
'status_label' => (string) ($item['status_label'] ?? 'Needs attention'),
|
|
'decision_label' => (string) ($item['decision_label'] ?? 'Review governance item'),
|
|
'reason_label' => (string) ($item['reason_label'] ?? 'Reason unavailable'),
|
|
'impact_label' => (string) ($item['impact_label'] ?? 'Impact unavailable'),
|
|
'owner_label' => (string) ($item['owner_label'] ?? 'Owner unavailable'),
|
|
'owner_state' => (string) ($item['owner_state'] ?? 'unavailable'),
|
|
'due_label' => (string) ($item['due_label'] ?? 'Due date unavailable'),
|
|
'due_state' => (string) ($item['due_state'] ?? 'unavailable'),
|
|
'evidence_label' => (string) ($item['evidence_label'] ?? 'Evidence unavailable'),
|
|
'evidence_state' => (string) ($item['evidence_state'] ?? 'unavailable'),
|
|
'evidence_path_label' => (string) ($item['evidence_path_label'] ?? 'Proof path unavailable'),
|
|
'evidence_path_url' => filled($item['evidence_path_url'] ?? null) ? (string) $item['evidence_path_url'] : null,
|
|
'exception_label' => (string) ($item['exception_label'] ?? 'Accepted-risk state unavailable'),
|
|
'exception_state' => (string) ($item['exception_state'] ?? 'unavailable'),
|
|
'primary_action_label' => (string) ($item['primary_action_label'] ?? 'Open source'),
|
|
'primary_action_url' => filled($item['primary_action_url'] ?? null)
|
|
? (string) $item['primary_action_url']
|
|
: (filled($item['destination_url'] ?? null) ? (string) $item['destination_url'] : null),
|
|
'source_url' => filled($item['destination_url'] ?? null) ? (string) $item['destination_url'] : null,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param \Illuminate\Support\Collection<int, array<string, mixed>> $entries
|
|
* @return list<array{label: string, value: string, description: string}>
|
|
*/
|
|
private function summaryCards(\Illuminate\Support\Collection $entries): array
|
|
{
|
|
$totalCount = (int) ($this->inboxPayload()['total_count'] ?? 0);
|
|
$selectedSection = $entries
|
|
->sortBy([
|
|
fn (array $entry): int => (int) ($entry['urgency_rank'] ?? 999),
|
|
fn (array $entry): string => (string) ($entry['headline'] ?? ''),
|
|
])
|
|
->first()['section_label'] ?? 'None';
|
|
$ownerGaps = $entries
|
|
->filter(fn (array $entry): bool => in_array((string) ($entry['owner_state'] ?? ''), ['missing', 'unavailable'], true))
|
|
->count();
|
|
$evidenceGaps = $entries
|
|
->filter(fn (array $entry): bool => in_array((string) ($entry['evidence_state'] ?? ''), ['missing', 'unavailable'], true))
|
|
->count();
|
|
|
|
return [
|
|
[
|
|
'label' => 'Visible decisions',
|
|
'value' => (string) $totalCount,
|
|
'description' => 'Repo-backed attention items in the current scope.',
|
|
],
|
|
[
|
|
'label' => 'Priority family',
|
|
'value' => (string) $selectedSection,
|
|
'description' => 'Highest-ranked visible preview item.',
|
|
],
|
|
[
|
|
'label' => 'Owner gaps in preview',
|
|
'value' => (string) $ownerGaps,
|
|
'description' => 'Preview items with missing or unavailable ownership.',
|
|
],
|
|
[
|
|
'label' => 'Evidence gaps in preview',
|
|
'value' => (string) $evidenceGaps,
|
|
'description' => 'Preview items without linked proof in the workbench.',
|
|
],
|
|
];
|
|
}
|
|
|
|
private function authorizeWorkspaceMembership(): void
|
|
{
|
|
$user = auth()->user();
|
|
$workspace = $this->workspace();
|
|
|
|
if (! $user instanceof User) {
|
|
abort(403);
|
|
}
|
|
|
|
if (! $workspace instanceof Workspace) {
|
|
throw new NotFoundHttpException;
|
|
}
|
|
|
|
$resolver = app(WorkspaceCapabilityResolver::class);
|
|
|
|
if (! $resolver->isMember($user, $workspace)) {
|
|
throw new NotFoundHttpException;
|
|
}
|
|
}
|
|
|
|
private function ensureAtLeastOneVisibleFamily(): void
|
|
{
|
|
if (
|
|
$this->hasVisibleOperationsFamily()
|
|
|| $this->visibleFindingTenants() !== []
|
|
|| $this->hasVisibleFindingExceptionsFamily()
|
|
|| $this->reviewTenants() !== []
|
|
|| $this->hasVisibleAlertsFamily()
|
|
) {
|
|
return;
|
|
}
|
|
|
|
abort(403);
|
|
}
|
|
|
|
private function ensureRequestedFamilyIsVisible(): void
|
|
{
|
|
if ($this->family === null) {
|
|
return;
|
|
}
|
|
|
|
if (in_array($this->family, collect($this->availableFamilies())->pluck('key')->all(), true)) {
|
|
return;
|
|
}
|
|
|
|
throw new NotFoundHttpException;
|
|
}
|
|
|
|
private function hasVisibleOperationsFamily(): bool
|
|
{
|
|
return $this->authorizedTenants() !== [];
|
|
}
|
|
|
|
private function hasVisibleAlertsFamily(): bool
|
|
{
|
|
if (is_bool($this->visibleAlertsFamily)) {
|
|
return $this->visibleAlertsFamily;
|
|
}
|
|
|
|
$user = auth()->user();
|
|
$workspace = $this->workspace();
|
|
|
|
if (! $user instanceof User || ! $workspace instanceof Workspace) {
|
|
return $this->visibleAlertsFamily = false;
|
|
}
|
|
|
|
return $this->visibleAlertsFamily = app(WorkspaceCapabilityResolver::class)->can($user, $workspace, Capabilities::ALERTS_VIEW);
|
|
}
|
|
|
|
private function hasVisibleFindingExceptionsFamily(): bool
|
|
{
|
|
if (is_bool($this->visibleFindingExceptionsFamily)) {
|
|
return $this->visibleFindingExceptionsFamily;
|
|
}
|
|
|
|
if ($this->authorizedTenants() === []) {
|
|
return $this->visibleFindingExceptionsFamily = false;
|
|
}
|
|
|
|
$user = auth()->user();
|
|
$workspace = $this->workspace();
|
|
|
|
if (! $user instanceof User || ! $workspace instanceof Workspace) {
|
|
return $this->visibleFindingExceptionsFamily = false;
|
|
}
|
|
|
|
return $this->visibleFindingExceptionsFamily = app(WorkspaceCapabilityResolver::class)
|
|
->can($user, $workspace, Capabilities::FINDING_EXCEPTION_APPROVE);
|
|
}
|
|
|
|
/**
|
|
* @return array<int, ManagedEnvironment>
|
|
*/
|
|
private function visibleFindingTenants(): array
|
|
{
|
|
if ($this->visibleFindingTenants !== null) {
|
|
return $this->visibleFindingTenants;
|
|
}
|
|
|
|
$user = auth()->user();
|
|
$tenants = $this->authorizedTenants();
|
|
|
|
if (! $user instanceof User || $tenants === []) {
|
|
return $this->visibleFindingTenants = [];
|
|
}
|
|
|
|
$resolver = app(CapabilityResolver::class);
|
|
$resolver->primeMemberships(
|
|
$user,
|
|
array_map(static fn (ManagedEnvironment $tenant): int => (int) $tenant->getKey(), $tenants),
|
|
);
|
|
|
|
return $this->visibleFindingTenants = array_values(array_filter(
|
|
$tenants,
|
|
fn (ManagedEnvironment $tenant): bool => $resolver->can($user, $tenant, Capabilities::TENANT_FINDINGS_VIEW),
|
|
));
|
|
}
|
|
|
|
/**
|
|
* @return array<int, ManagedEnvironment>
|
|
*/
|
|
private function reviewTenants(): array
|
|
{
|
|
if ($this->reviewTenants !== null) {
|
|
return $this->reviewTenants;
|
|
}
|
|
|
|
$user = auth()->user();
|
|
$workspace = $this->workspace();
|
|
|
|
if (! $user instanceof User || ! $workspace instanceof Workspace) {
|
|
return $this->reviewTenants = [];
|
|
}
|
|
|
|
$service = app(EnvironmentReviewRegisterService::class);
|
|
|
|
if (! $service->canAccessWorkspace($user, $workspace)) {
|
|
return $this->reviewTenants = [];
|
|
}
|
|
|
|
return $this->reviewTenants = $service->authorizedTenants($user, $workspace);
|
|
}
|
|
|
|
/**
|
|
* @return array<int, ManagedEnvironment>
|
|
*/
|
|
private function authorizedTenants(): array
|
|
{
|
|
if ($this->authorizedTenants !== null) {
|
|
return $this->authorizedTenants;
|
|
}
|
|
|
|
$user = auth()->user();
|
|
$workspace = $this->workspace();
|
|
|
|
if (! $user instanceof User || ! $workspace instanceof Workspace) {
|
|
return $this->authorizedTenants = [];
|
|
}
|
|
|
|
return $this->authorizedTenants = $user->accessibleManagedEnvironmentsQuery((int) $workspace->getKey())
|
|
->where('managed_environments.lifecycle_status', 'active')
|
|
->orderBy('managed_environments.name')
|
|
->get(['managed_environments.id', 'managed_environments.name', 'managed_environments.slug', 'managed_environments.workspace_id'])
|
|
->all();
|
|
}
|
|
|
|
private function applyRequestedTenantPrefilter(): void
|
|
{
|
|
$workspace = $this->workspace();
|
|
|
|
if (! $workspace instanceof Workspace) {
|
|
return;
|
|
}
|
|
|
|
$filter = WorkspaceHubEnvironmentFilter::fromRequest(request(), $workspace);
|
|
|
|
if (! $filter instanceof WorkspaceHubEnvironmentFilter) {
|
|
return;
|
|
}
|
|
|
|
$environmentId = $filter->environmentId();
|
|
|
|
foreach ($this->authorizedTenants() as $tenant) {
|
|
if ((int) $tenant->getKey() === $environmentId) {
|
|
$this->tenantId = $environmentId;
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
throw new NotFoundHttpException;
|
|
}
|
|
|
|
private function resolveRequestedFamily(): ?string
|
|
{
|
|
$family = request()->query('family');
|
|
|
|
if (! is_string($family)) {
|
|
return null;
|
|
}
|
|
|
|
return in_array($family, [
|
|
'assigned_findings',
|
|
'intake_findings',
|
|
'finding_exceptions',
|
|
'stale_operations',
|
|
'alert_delivery_failures',
|
|
'review_follow_up',
|
|
], true) ? $family : null;
|
|
}
|
|
|
|
private function workspace(): ?Workspace
|
|
{
|
|
if ($this->workspace instanceof Workspace) {
|
|
return $this->workspace;
|
|
}
|
|
|
|
$workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request());
|
|
|
|
if (! is_int($workspaceId)) {
|
|
return null;
|
|
}
|
|
|
|
return $this->workspace = Workspace::query()->whereKey($workspaceId)->first();
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function inboxPayload(): array
|
|
{
|
|
if (is_array($this->inboxPayload)) {
|
|
return $this->inboxPayload;
|
|
}
|
|
|
|
$user = auth()->user();
|
|
$workspace = $this->workspace();
|
|
|
|
if (! $user instanceof User || ! $workspace instanceof Workspace) {
|
|
return $this->inboxPayload = [
|
|
'sections' => [],
|
|
'available_families' => [],
|
|
'family_counts' => [],
|
|
'total_count' => 0,
|
|
];
|
|
}
|
|
|
|
return $this->inboxPayload = app(GovernanceInboxSectionBuilder::class)->build(
|
|
user: $user,
|
|
workspace: $workspace,
|
|
authorizedTenants: $this->authorizedTenants(),
|
|
visibleFindingTenants: $this->visibleFindingTenants(),
|
|
reviewTenants: $this->reviewTenants(),
|
|
canViewAlerts: $this->hasVisibleAlertsFamily(),
|
|
canViewFindingExceptions: $this->hasVisibleFindingExceptionsFamily(),
|
|
selectedTenant: $this->selectedTenant(),
|
|
selectedFamily: $this->family,
|
|
navigationContext: $this->navigationContext(),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function unfilteredInboxPayload(): array
|
|
{
|
|
if (is_array($this->unfilteredInboxPayload)) {
|
|
return $this->unfilteredInboxPayload;
|
|
}
|
|
|
|
$user = auth()->user();
|
|
$workspace = $this->workspace();
|
|
|
|
if (! $user instanceof User || ! $workspace instanceof Workspace) {
|
|
return $this->unfilteredInboxPayload = [
|
|
'sections' => [],
|
|
'available_families' => [],
|
|
'family_counts' => [],
|
|
'total_count' => 0,
|
|
];
|
|
}
|
|
|
|
return $this->unfilteredInboxPayload = app(GovernanceInboxSectionBuilder::class)->build(
|
|
user: $user,
|
|
workspace: $workspace,
|
|
authorizedTenants: $this->authorizedTenants(),
|
|
visibleFindingTenants: $this->visibleFindingTenants(),
|
|
reviewTenants: $this->reviewTenants(),
|
|
canViewAlerts: $this->hasVisibleAlertsFamily(),
|
|
canViewFindingExceptions: $this->hasVisibleFindingExceptionsFamily(),
|
|
selectedTenant: null,
|
|
selectedFamily: null,
|
|
navigationContext: $this->navigationContext(),
|
|
);
|
|
}
|
|
|
|
private function selectedTenant(): ?ManagedEnvironment
|
|
{
|
|
if (! is_int($this->tenantId)) {
|
|
return null;
|
|
}
|
|
|
|
foreach ($this->authorizedTenants() as $tenant) {
|
|
if ((int) $tenant->getKey() === $this->tenantId) {
|
|
return $tenant;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function tenantFilterAloneExcludesRows(): bool
|
|
{
|
|
if (! is_int($this->tenantId) || $this->family !== null) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->sections() !== []) {
|
|
return false;
|
|
}
|
|
|
|
return (int) ($this->unfilteredInboxPayload()['total_count'] ?? 0) > 0;
|
|
}
|
|
}
|