TenantAtlas/apps/platform/app/Filament/Pages/CrossEnvironmentComparePage.php
ahmido 292d555eac refactor: consolidate internal tenant model naming (#355)
## Summary
- consolidate internal platform naming from `Tenant` to `Environment` / `ManagedEnvironment` across models, controllers, services, and Filament resources
- rename environment-scoped UI surfaces such as dashboards, chooser flows, navigation, and related widgets to match the updated environment-first domain language
- align middleware, onboarding/review lifecycle services, jobs, and route/context controllers with the new environment-scoped architecture

## Validation
- not rerun as part of this commit/push/PR request

## Notes
- branch is 1 commit ahead of `platform-dev`
- main commit: `refactor: consolidate internal tenant model naming`

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #355
2026-05-14 11:13:28 +00:00

856 lines
30 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Filament\Pages;
use App\Models\InventoryItem;
use App\Models\ManagedEnvironment;
use App\Models\User;
use App\Models\Workspace;
use App\Services\Audit\WorkspaceAuditLogger;
use App\Services\Auth\CapabilityResolver;
use App\Services\Auth\WorkspaceCapabilityResolver;
use App\Services\PortfolioCompare\CrossEnvironmentPromotionExecutionService;
use App\Support\Auth\Capabilities;
use App\Support\ManagedEnvironmentLinks;
use App\Support\Navigation\CanonicalNavigationContext;
use App\Support\OperationalControls\OperationalControlBlockedException;
use App\Support\OperationRunLinks;
use App\Support\OpsUx\OpsUxBrowserEvents;
use App\Support\OpsUx\ProviderOperationStartResultPresenter;
use App\Support\PortfolioCompare\CrossEnvironmentComparePreviewBuilder;
use App\Support\PortfolioCompare\CrossEnvironmentCompareSelection;
use App\Support\PortfolioCompare\CrossEnvironmentPromotionPreflight;
use App\Support\Rbac\WorkspaceUiEnforcement;
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceProfile;
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceSlot;
use App\Support\Workspaces\WorkspaceContext;
use BackedEnum;
use DomainException;
use Filament\Actions\Action;
use Filament\Notifications\Notification;
use Filament\Forms\Components\Select;
use Filament\Forms\Concerns\InteractsWithForms;
use Filament\Forms\Contracts\HasForms;
use Filament\Pages\Page;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Schema;
use InvalidArgumentException;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use UnitEnum;
class CrossEnvironmentComparePage extends Page implements HasForms
{
use InteractsWithForms;
private const string SOURCE_ENVIRONMENT_QUERY_KEY = 'source_environment_id';
private const string TARGET_ENVIRONMENT_QUERY_KEY = 'target_environment_id';
private const string POLICY_TYPE_QUERY_KEY = 'policy_type';
protected static bool $isDiscovered = false;
protected static bool $shouldRegisterNavigation = false;
protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-scale';
protected static string|UnitEnum|null $navigationGroup = 'Governance';
protected static ?string $title = 'Cross-environment compare';
protected static ?string $slug = 'cross-environment-compare';
protected string $view = 'filament.pages.cross-environment-compare';
public ?string $sourceEnvironmentId = null;
public ?string $targetEnvironmentId = null;
/**
* @var list<string>
*/
public array $selectedPolicyTypes = [];
/**
* @var array<string, mixed>|null
*/
public ?array $navigationContextPayload = null;
/**
* @var array<string, mixed>|null
*/
public ?array $preview = null;
/**
* @var array<string, mixed>|null
*/
public ?array $preflight = null;
public ?string $selectionMessage = null;
public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
{
return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly)
->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions preserve return navigation, environment drill-downs, and one dominant promotion-preflight action.')
->exempt(ActionSurfaceSlot::InspectAffordance, 'The compare page uses explicit selection controls instead of row-click inspection.')
->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'Cross-environment compare renders focused subject summaries instead of row-level overflow actions.')
->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The compare page has no bulk actions.')
->satisfy(ActionSurfaceSlot::ListEmptyState, 'The compare page explains when a selection is incomplete or invalid before any preview exists.')
->exempt(ActionSurfaceSlot::DetailHeader, 'Cross-environment compare is a workspace decision page, not a record detail header.');
}
public function mount(): void
{
$this->authorizePageAccess();
$this->navigationContextPayload = is_array(request()->query('nav')) ? request()->query('nav') : null;
$this->hydrateSelectionFromRequest();
$this->refreshPreview();
$this->form->fill($this->formState());
}
public function form(Schema $schema): Schema
{
return $schema
->schema([
Grid::make([
'default' => 1,
'xl' => 3,
])
->schema([
Select::make('sourceEnvironmentId')
->label('Source environment')
->options(fn (): array => $this->environmentOptions())
->searchable()
->preload()
->native(false)
->placeholder('Select a source environment')
->extraFieldWrapperAttributes(['data-testid' => 'cross-environment-source'])
->extraInputAttributes(['data-testid' => 'cross-environment-source']),
Select::make('targetEnvironmentId')
->label('Target environment')
->options(fn (): array => $this->environmentOptions())
->searchable()
->preload()
->native(false)
->placeholder('Select a target environment')
->extraFieldWrapperAttributes(['data-testid' => 'cross-environment-target'])
->extraInputAttributes(['data-testid' => 'cross-environment-target']),
Select::make('selectedPolicyTypes')
->label('Governed subjects')
->options(fn (): array => $this->policyTypeOptions())
->multiple()
->searchable()
->preload()
->native(false)
->placeholder('All governed subjects')
->helperText(fn (): ?string => $this->policyTypeOptions() === []
? 'Governed subject filters appear after authorized environment inventory exists in the active workspace.'
: null)
->extraFieldWrapperAttributes(['data-testid' => 'cross-environment-policy-types'])
->extraInputAttributes(['data-testid' => 'cross-environment-policy-types']),
]),
]);
}
/**
* @return array<Action>
*/
protected function getHeaderActions(): array
{
$actions = [];
$navigationContext = $this->navigationContext();
if ($navigationContext?->backLinkLabel !== null && $navigationContext->backLinkUrl !== null) {
$actions[] = Action::make('return_to_origin')
->label($navigationContext->backLinkLabel)
->icon('heroicon-o-arrow-left')
->color('gray')
->url($navigationContext->backLinkUrl);
}
$sourceEnvironment = $this->selectedSourceEnvironment();
if ($sourceEnvironment instanceof ManagedEnvironment) {
$actions[] = Action::make('open_source_environment')
->label('Open source environment')
->icon('heroicon-o-arrow-top-right-on-square')
->color('gray')
->url(ManagedEnvironmentLinks::viewUrl($sourceEnvironment));
}
$targetEnvironment = $this->selectedTargetEnvironment();
if ($targetEnvironment instanceof ManagedEnvironment) {
$actions[] = Action::make('open_target_environment')
->label('Open target environment')
->icon('heroicon-o-arrow-top-right-on-square')
->color('gray')
->url(ManagedEnvironmentLinks::viewUrl($targetEnvironment));
}
$preflightAction = Action::make('generatePromotionPreflight')
->label('Generate promotion preflight')
->icon('heroicon-o-sparkles')
->color('primary')
->visible(fn (): bool => ! is_array($this->preflight))
->disabled(fn (): bool => $this->preflightDisabledReason() !== null)
->tooltip(fn (): ?string => $this->preflightDisabledReason())
->action(fn (): mixed => $this->generatePromotionPreflight());
$preflightAction = WorkspaceUiEnforcement::forAction(
$preflightAction,
fn (): ?Workspace => $this->workspace(),
)
->requireCapability(Capabilities::WORKSPACE_BASELINES_MANAGE)
->preserveVisibility()
->preserveDisabled()
->tooltip('You need workspace baseline manage access to generate a promotion preflight.')
->apply()
->tooltip(function (): ?string {
$user = auth()->user();
$workspace = $this->workspace();
if ($user instanceof User && $workspace instanceof Workspace) {
/** @var WorkspaceCapabilityResolver $resolver */
$resolver = app(WorkspaceCapabilityResolver::class);
if ($resolver->isMember($user, $workspace)
&& ! $resolver->can($user, $workspace, Capabilities::WORKSPACE_BASELINES_MANAGE)) {
return 'You need workspace baseline manage access to generate a promotion preflight.';
}
}
return $this->preflightDisabledReason();
});
$actions[] = $preflightAction;
$actions[] = Action::make('executePromotion')
->label('Execute promotion')
->icon('heroicon-o-play')
->color('warning')
->visible(fn (): bool => is_array($this->preflight))
->requiresConfirmation()
->modalHeading('Execute promotion')
->modalDescription(fn (): string => $this->executePromotionConfirmationDescription())
->modalSubmitActionLabel('Queue promotion')
->disabled(fn (): bool => $this->executePromotionDisabledReason() !== null)
->tooltip(fn (): ?string => $this->executePromotionDisabledReason())
->action(fn (): mixed => $this->executePromotion());
return $actions;
}
public function applySelection(): void
{
$this->selectionMessage = null;
$this->preflight = null;
$this->sourceEnvironmentId = $this->normalizeEnvironmentIdentifier($this->sourceEnvironmentId);
$this->targetEnvironmentId = $this->normalizeEnvironmentIdentifier($this->targetEnvironmentId);
$this->selectedPolicyTypes = $this->normalizePolicyTypes($this->selectedPolicyTypes);
if ($this->sourceEnvironmentId !== null
&& $this->targetEnvironmentId !== null
&& $this->sourceEnvironmentId === $this->targetEnvironmentId) {
$this->selectionMessage = 'Choose two different environments.';
$this->addError('targetEnvironmentId', $this->selectionMessage);
return;
}
$this->redirect($this->selectionUrl(), navigate: true);
}
public function generatePromotionPreflight(): void
{
$this->authorizePageAccess();
$this->authorizePreflightExecution();
if ($this->preview === null) {
$this->refreshPreview();
}
if ($this->preview === null) {
return;
}
$selection = $this->compareSelection();
if (! $selection instanceof CrossEnvironmentCompareSelection) {
return;
}
$this->preflight = app(CrossEnvironmentPromotionPreflight::class)->build($this->preview);
$workspace = $this->workspace();
$user = auth()->user();
if ($workspace instanceof Workspace && $user instanceof User) {
app(WorkspaceAuditLogger::class)->logCrossEnvironmentPromotionPreflightGenerated(
workspace: $workspace,
sourceEnvironment: $selection->sourceEnvironment,
targetEnvironment: $selection->targetEnvironment,
preflight: $this->preflight,
actor: $user,
);
}
}
public function executePromotion(): void
{
$this->authorizePageAccess();
$this->authorizePromotionExecution();
if (! is_array($this->preview) || ! is_array($this->preflight)) {
Notification::make()
->title('Promotion execution unavailable')
->body('Generate a current promotion preflight before executing promotion.')
->warning()
->send();
return;
}
$selection = $this->compareSelection();
$user = auth()->user();
if (! $selection instanceof CrossEnvironmentCompareSelection || ! $user instanceof User) {
Notification::make()
->title('Promotion execution unavailable')
->body('Refresh the compare selection before executing promotion.')
->warning()
->send();
return;
}
try {
$result = app(CrossEnvironmentPromotionExecutionService::class)->start(
selection: $selection,
preview: $this->preview,
preflight: $this->preflight,
actor: $user,
);
} catch (OperationalControlBlockedException $exception) {
Notification::make()
->title($exception->title())
->body($exception->getMessage())
->warning()
->send();
return;
} catch (DomainException|InvalidArgumentException $exception) {
Notification::make()
->title('Promotion execution unavailable')
->body($exception->getMessage())
->warning()
->send();
return;
}
$notification = app(ProviderOperationStartResultPresenter::class)->notification(
result: $result,
blockedTitle: 'Promotion execution blocked',
runUrl: OperationRunLinks::tenantlessView($result->run),
scopeBusyTitle: 'Promotion scope busy',
scopeBusyBody: 'Another promotion or restore operation is already active for this target scope. Open the active operation for progress and next steps.',
);
if (in_array($result->status, ['started', 'deduped', 'scope_busy'], true)) {
OpsUxBrowserEvents::dispatchRunEnqueued($this);
}
$notification->send();
}
public function clearSelectionUrl(): string
{
return static::getUrl($this->routeParameters([
self::SOURCE_ENVIRONMENT_QUERY_KEY => null,
self::TARGET_ENVIRONMENT_QUERY_KEY => null,
self::POLICY_TYPE_QUERY_KEY => null,
]), panel: 'admin');
}
public function selectionUrl(): string
{
return static::getUrl($this->routeParameters(), panel: 'admin');
}
public static function launchUrl(
?ManagedEnvironment $sourceEnvironment = null,
?ManagedEnvironment $targetEnvironment = null,
?CanonicalNavigationContext $navigationContext = null,
): string {
$parameters = [];
if ($sourceEnvironment instanceof ManagedEnvironment) {
$parameters[self::SOURCE_ENVIRONMENT_QUERY_KEY] = (int) $sourceEnvironment->getKey();
}
if ($targetEnvironment instanceof ManagedEnvironment) {
$parameters[self::TARGET_ENVIRONMENT_QUERY_KEY] = (int) $targetEnvironment->getKey();
}
if ($navigationContext instanceof CanonicalNavigationContext) {
$parameters = array_replace($parameters, $navigationContext->toQuery());
}
return static::getUrl($parameters, panel: 'admin');
}
public function hasActiveSelection(): bool
{
return $this->sourceEnvironmentId !== null
|| $this->targetEnvironmentId !== null
|| $this->selectedPolicyTypes !== [];
}
public function stateColor(string $state): string
{
return match ($state) {
'match', 'ready' => 'success',
'different', 'manual_mapping_required' => 'warning',
'missing' => 'info',
'ambiguous' => 'gray',
'blocked' => 'danger',
default => 'gray',
};
}
public function stateLabel(string $value): string
{
return Str::headline(str_replace('_', ' ', $value));
}
public function reasonLabel(string $reasonCode): string
{
return Str::headline(str_replace('_', ' ', $reasonCode));
}
public function sourceEnvironmentUrl(): ?string
{
$environment = $this->selectedSourceEnvironment();
if (! $environment instanceof ManagedEnvironment) {
return null;
}
return ManagedEnvironmentLinks::viewUrl($environment);
}
public function targetEnvironmentUrl(): ?string
{
$environment = $this->selectedTargetEnvironment();
if (! $environment instanceof ManagedEnvironment) {
return null;
}
return ManagedEnvironmentLinks::viewUrl($environment);
}
/**
* @return array<string, mixed>
*/
private function formState(): array
{
return [
'sourceEnvironmentId' => $this->sourceEnvironmentId,
'targetEnvironmentId' => $this->targetEnvironmentId,
'selectedPolicyTypes' => $this->selectedPolicyTypes,
];
}
private function hydrateSelectionFromRequest(): void
{
$this->sourceEnvironmentId = $this->normalizeEnvironmentIdentifier(request()->query(self::SOURCE_ENVIRONMENT_QUERY_KEY));
$this->targetEnvironmentId = $this->normalizeEnvironmentIdentifier(request()->query(self::TARGET_ENVIRONMENT_QUERY_KEY));
$this->selectedPolicyTypes = $this->normalizePolicyTypes(request()->query(self::POLICY_TYPE_QUERY_KEY, []));
}
private function refreshPreview(): void
{
$this->selectionMessage = null;
$this->preview = null;
$this->preflight = null;
$selection = $this->compareSelection();
if (! $selection instanceof CrossEnvironmentCompareSelection) {
return;
}
$this->preview = app(CrossEnvironmentComparePreviewBuilder::class)->build($selection);
}
private function authorizePageAccess(): void
{
$user = auth()->user();
$workspace = $this->workspace();
if (! $user instanceof User) {
abort(403);
}
if (! $workspace instanceof Workspace) {
abort(404);
}
/** @var WorkspaceCapabilityResolver $resolver */
$resolver = app(WorkspaceCapabilityResolver::class);
if (! $resolver->isMember($user, $workspace)) {
abort(404);
}
if (! $resolver->can($user, $workspace, Capabilities::WORKSPACE_BASELINES_VIEW)) {
abort(403);
}
}
private function authorizePreflightExecution(): void
{
$user = auth()->user();
$workspace = $this->workspace();
if (! $user instanceof User) {
abort(403);
}
if (! $workspace instanceof Workspace) {
abort(404);
}
/** @var WorkspaceCapabilityResolver $resolver */
$resolver = app(WorkspaceCapabilityResolver::class);
if (! $resolver->isMember($user, $workspace)) {
abort(404);
}
if (! $resolver->can($user, $workspace, Capabilities::WORKSPACE_BASELINES_MANAGE)) {
abort(403);
}
}
private function authorizePromotionExecution(): void
{
$this->authorizePreflightExecution();
$user = auth()->user();
if (! $user instanceof User) {
abort(403);
}
$targetEnvironment = $this->selectedTargetEnvironment();
if (! $targetEnvironment instanceof ManagedEnvironment) {
abort(404);
}
/** @var CapabilityResolver $resolver */
$resolver = app(CapabilityResolver::class);
if (! $resolver->can($user, $targetEnvironment, Capabilities::TENANT_MANAGE)) {
abort(403);
}
}
private function compareSelection(): ?CrossEnvironmentCompareSelection
{
$sourceEnvironment = $this->selectedSourceEnvironment();
$targetEnvironment = $this->selectedTargetEnvironment();
if (! $sourceEnvironment instanceof ManagedEnvironment || ! $targetEnvironment instanceof ManagedEnvironment) {
return null;
}
if ((int) $sourceEnvironment->getKey() === (int) $targetEnvironment->getKey()) {
$this->selectionMessage = 'Choose two different environments.';
return null;
}
return new CrossEnvironmentCompareSelection(
sourceEnvironment: $sourceEnvironment,
targetEnvironment: $targetEnvironment,
policyTypes: $this->selectedPolicyTypes,
);
}
private function selectedSourceEnvironment(): ?ManagedEnvironment
{
if ($this->sourceEnvironmentId === null) {
return null;
}
return $this->resolveAuthorizedEnvironment($this->sourceEnvironmentId);
}
private function selectedTargetEnvironment(): ?ManagedEnvironment
{
if ($this->targetEnvironmentId === null) {
return null;
}
return $this->resolveAuthorizedEnvironment($this->targetEnvironmentId);
}
private function resolveAuthorizedEnvironment(string $environmentId): ManagedEnvironment
{
$workspace = $this->workspace();
$user = auth()->user();
if (! $workspace instanceof Workspace || ! $user instanceof User) {
abort(404);
}
$environment = ManagedEnvironment::query()
->where('workspace_id', (int) $workspace->getKey())
->whereKey((int) $environmentId)
->first();
if (! $environment instanceof ManagedEnvironment) {
abort(404);
}
/** @var CapabilityResolver $resolver */
$resolver = app(CapabilityResolver::class);
if (! $user->canAccessTenant($environment) || ! $resolver->can($user, $environment, Capabilities::TENANT_VIEW)) {
abort(404);
}
return $environment;
}
/**
* @return array<string, string>
*/
private function environmentOptions(): array
{
$workspace = $this->workspace();
$user = auth()->user();
if (! $workspace instanceof Workspace || ! $user instanceof User) {
return [];
}
/** @var CapabilityResolver $resolver */
$resolver = app(CapabilityResolver::class);
$environments = $user->accessibleManagedEnvironmentsQuery((int) $workspace->getKey())
->select('managed_environments.*')
->orderBy('managed_environments.name')
->get();
$resolver->primeMemberships($user, $environments->modelKeys());
return $environments
->filter(fn (ManagedEnvironment $environment): bool => $resolver->can($user, $environment, Capabilities::TENANT_VIEW))
->mapWithKeys(fn (ManagedEnvironment $environment): array => [
(string) $environment->getKey() => (string) $environment->name,
])
->all();
}
/**
* @return array<string, string>
*/
private function policyTypeOptions(): array
{
$environmentIds = array_map(static fn (string $environmentId): int => (int) $environmentId, array_keys($this->environmentOptions()));
if ($environmentIds === []) {
return [];
}
return InventoryItem::query()
->whereIn('managed_environment_id', $environmentIds)
->whereNotNull('policy_type')
->where('policy_type', '!=', '')
->distinct()
->orderBy('policy_type')
->pluck('policy_type')
->mapWithKeys(fn (string $policyType): array => [
$policyType => Str::headline($policyType),
])
->all();
}
private function preflightDisabledReason(): ?string
{
if ($this->selectionMessage !== null) {
return $this->selectionMessage;
}
if (! is_array($this->preview)) {
return 'Select an authorized source and target environment to generate a promotion preflight.';
}
if ((int) data_get($this->preview, 'summary.total', 0) === 0) {
return 'No governed subjects are available for this compare selection yet.';
}
return null;
}
private function executePromotionDisabledReason(): ?string
{
if ($this->selectionMessage !== null) {
return $this->selectionMessage;
}
if (! is_array($this->preview)) {
return 'Run compare preview before executing promotion.';
}
if (! is_array($this->preflight)) {
return 'Generate a current promotion preflight before executing promotion.';
}
if ((int) data_get($this->preflight, 'summary.ready', 0) <= 0) {
return 'Current promotion preflight has no ready governed subjects to execute.';
}
$user = auth()->user();
$workspace = $this->workspace();
if ($user instanceof User && $workspace instanceof Workspace) {
/** @var WorkspaceCapabilityResolver $workspaceResolver */
$workspaceResolver = app(WorkspaceCapabilityResolver::class);
if ($workspaceResolver->isMember($user, $workspace)
&& ! $workspaceResolver->can($user, $workspace, Capabilities::WORKSPACE_BASELINES_MANAGE)) {
return 'You need workspace baseline manage access to execute promotion.';
}
$targetEnvironment = $this->selectedTargetEnvironment();
if ($targetEnvironment instanceof ManagedEnvironment) {
/** @var CapabilityResolver $resolver */
$resolver = app(CapabilityResolver::class);
if (! $resolver->can($user, $targetEnvironment, Capabilities::TENANT_MANAGE)) {
return 'You need target environment manage access to execute promotion.';
}
}
}
return null;
}
private function executePromotionConfirmationDescription(): string
{
$selection = $this->compareSelection();
$ready = (int) data_get($this->preflight, 'summary.ready', 0);
$blocked = (int) data_get($this->preflight, 'summary.blocked', 0);
$manualMappingRequired = (int) data_get($this->preflight, 'summary.manual_mapping_required', 0);
$excluded = $blocked + $manualMappingRequired;
$sourceEnvironmentName = $selection?->sourceEnvironment->name ?? 'Source environment';
$targetEnvironmentName = $selection?->targetEnvironment->name ?? 'Target environment';
return sprintf(
'Queue one promotion run from %s to %s for %d ready governed subject%s. %d subject%s remain excluded on the compare page.',
$sourceEnvironmentName,
$targetEnvironmentName,
$ready,
$ready === 1 ? '' : 's',
$excluded,
$excluded === 1 ? '' : 's',
);
}
/**
* @param mixed $value
*/
private function normalizeEnvironmentIdentifier(mixed $value): ?string
{
if (! is_string($value) && ! is_int($value)) {
return null;
}
$normalized = trim((string) $value);
return is_numeric($normalized) && (int) $normalized > 0 ? (string) (int) $normalized : null;
}
/**
* @param mixed $value
* @return list<string>
*/
private function normalizePolicyTypes(mixed $value): array
{
$allowed = array_fill_keys(array_keys($this->policyTypeOptions()), true);
$values = match (true) {
is_string($value) && $value !== '' => [$value],
is_array($value) => $value,
default => [],
};
return array_values(array_filter(array_unique(array_map(
static fn (mixed $item): string => is_string($item) ? trim($item) : '',
$values,
)), static fn (string $item): bool => $item !== '' && isset($allowed[$item])));
}
/**
* @param array<string, mixed> $overrides
* @return array<string, mixed>
*/
private function routeParameters(array $overrides = []): array
{
$parameters = [
self::SOURCE_ENVIRONMENT_QUERY_KEY => $this->sourceEnvironmentId,
self::TARGET_ENVIRONMENT_QUERY_KEY => $this->targetEnvironmentId,
self::POLICY_TYPE_QUERY_KEY => $this->selectedPolicyTypes,
];
if (is_array($this->navigationContextPayload)) {
$parameters['nav'] = $this->navigationContextPayload;
}
foreach ($overrides as $key => $value) {
$parameters[$key] = $value;
}
return array_filter($parameters, static fn (mixed $value): bool => $value !== null && $value !== '' && $value !== []);
}
private function navigationContext(): ?CanonicalNavigationContext
{
if (! is_array($this->navigationContextPayload)) {
return CanonicalNavigationContext::fromRequest(request());
}
return CanonicalNavigationContext::fromPayload($this->navigationContextPayload);
}
private function workspace(): ?Workspace
{
$workspaceId = app(WorkspaceContext::class)->currentWorkspaceId(request());
if (! is_int($workspaceId)) {
return null;
}
$workspace = Workspace::query()->whereKey($workspaceId)->first();
return $workspace instanceof Workspace ? $workspace : null;
}
}