diff --git a/apps/platform/app/Filament/Pages/Operations/TenantlessOperationRunViewer.php b/apps/platform/app/Filament/Pages/Operations/TenantlessOperationRunViewer.php index 7d4b365f..c83eb20b 100644 --- a/apps/platform/app/Filament/Pages/Operations/TenantlessOperationRunViewer.php +++ b/apps/platform/app/Filament/Pages/Operations/TenantlessOperationRunViewer.php @@ -584,9 +584,16 @@ private function relatedLinks(bool $fresh = false): array $resolver = app(RelatedNavigationResolver::class); - return $fresh + $links = $fresh ? $resolver->operationLinksFresh($this->run, $this->relatedLinksTenant()) : $resolver->operationLinks($this->run, $this->relatedLinksTenant()); + + unset( + $links[OperationRunLinks::collectionLabel()], + $links[OperationRunLinks::openCollectionLabel()], + ); + + return $links; } private function lifecycleAttentionSummary(bool $fresh = false): ?string diff --git a/apps/platform/app/Filament/Pages/Reviews/ReviewRegister.php b/apps/platform/app/Filament/Pages/Reviews/ReviewRegister.php index d6e7666a..87690d1e 100644 --- a/apps/platform/app/Filament/Pages/Reviews/ReviewRegister.php +++ b/apps/platform/app/Filament/Pages/Reviews/ReviewRegister.php @@ -94,7 +94,7 @@ protected function getHeaderActions(): array ->color('gray') ->visible(fn (): bool => $this->hasActiveFilters()) ->action(function (): void { - $this->resetTable(); + $this->clearRegisterFilters(); }), ]; } @@ -209,7 +209,7 @@ public function table(Table $table): Table ->label('Clear filters') ->icon('heroicon-o-x-mark') ->color('gray') - ->action(fn (): mixed => $this->resetTable()), + ->action(fn (): mixed => $this->clearRegisterFilters()), ]); } @@ -311,9 +311,29 @@ private function applyRequestedTenantPrefilter(): void private function hasActiveFilters(): bool { - $filters = array_filter((array) $this->tableFilters); + return $this->currentTenantFilterId() !== null + || is_string(data_get($this->tableFilters, 'status.value')) + || is_string(data_get($this->tableFilters, 'completeness_state.value')) + || is_string(data_get($this->tableFilters, 'published_state.value')) + || filled(data_get($this->tableFilters, 'review_date.from')) + || filled(data_get($this->tableFilters, 'review_date.until')); + } - return $filters !== []; + private function clearRegisterFilters(): void + { + app(WorkspaceContext::class)->clearLastTenantId(request()); + $this->removeTableFilters(); + } + + private function currentTenantFilterId(): ?int + { + $tenantFilter = data_get($this->tableFilters, 'tenant_id.value'); + + if (! is_numeric($tenantFilter)) { + $tenantFilter = data_get(session()->get($this->getTableFiltersSessionKey(), []), 'tenant_id.value'); + } + + return is_numeric($tenantFilter) ? (int) $tenantFilter : null; } private function workspace(): ?Workspace diff --git a/apps/platform/tests/Feature/Operations/TenantlessOperationRunViewerTest.php b/apps/platform/tests/Feature/Operations/TenantlessOperationRunViewerTest.php index 0e5b4684..ddc5cfe4 100644 --- a/apps/platform/tests/Feature/Operations/TenantlessOperationRunViewerTest.php +++ b/apps/platform/tests/Feature/Operations/TenantlessOperationRunViewerTest.php @@ -2,6 +2,9 @@ declare(strict_types=1); +use App\Filament\Pages\Operations\TenantlessOperationRunViewer; +use App\Models\BaselineProfile; +use App\Models\BaselineSnapshot; use App\Models\OperationRun; use App\Models\Tenant; use App\Models\User; @@ -624,6 +627,52 @@ ->assertSee('Related drilldown'); }); +it('keeps operations-list navigation out of the related drilldown lane', function (): void { + $tenant = Tenant::factory()->create(); + [$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner'); + + $profile = BaselineProfile::factory()->active()->create([ + 'workspace_id' => (int) $tenant->workspace_id, + ]); + + $snapshot = BaselineSnapshot::factory()->complete()->create([ + 'workspace_id' => (int) $tenant->workspace_id, + 'baseline_profile_id' => (int) $profile->getKey(), + ]); + + $run = OperationRun::factory()->create([ + 'tenant_id' => (int) $tenant->getKey(), + 'workspace_id' => (int) $tenant->workspace_id, + 'type' => 'baseline_compare', + 'status' => OperationRunStatus::Completed->value, + 'outcome' => OperationRunOutcome::Succeeded->value, + 'context' => [ + 'baseline_profile_id' => (int) $profile->getKey(), + 'baseline_snapshot_id' => (int) $snapshot->getKey(), + ], + ]); + + Filament::setTenant($tenant, true); + session()->put(WorkspaceContext::SESSION_KEY, (int) $tenant->workspace_id); + + $this->actingAs($user); + + Livewire::test(TenantlessOperationRunViewer::class, ['run' => $run]) + ->assertActionVisible('view_baseline_profile') + ->assertActionVisible('view_snapshot') + ->assertActionDoesNotExist('operations') + ->assertActionDoesNotExist('open_operations'); + + $this + ->withSession([ + WorkspaceContext::SESSION_KEY => (int) $tenant->workspace_id, + ]) + ->get(route('admin.operations.view', ['run' => (int) $run->getKey()])) + ->assertSuccessful() + ->assertSee('Monitoring detail') + ->assertSee('Open keeps secondary drilldowns grouped under one control: View baseline profile, View snapshot.'); +}); + it('renders shared polling markup for active tenantless runs', function (string $status, int $ageSeconds): void { $workspace = Workspace::factory()->create(); $user = User::factory()->create(); diff --git a/apps/platform/tests/Feature/TenantReview/TenantReviewRegisterTest.php b/apps/platform/tests/Feature/TenantReview/TenantReviewRegisterTest.php index 21005c57..b3a1be69 100644 --- a/apps/platform/tests/Feature/TenantReview/TenantReviewRegisterTest.php +++ b/apps/platform/tests/Feature/TenantReview/TenantReviewRegisterTest.php @@ -67,6 +67,42 @@ ->assertSee('Clear filters'); }); +it('clears the remembered tenant prefilter from the review register', function (): void { + $tenantA = Tenant::factory()->create(['name' => 'Alpha Tenant']); + [$user, $tenantA] = createUserWithTenant(tenant: $tenantA, role: 'owner'); + + $tenantB = Tenant::factory()->create([ + 'workspace_id' => (int) $tenantA->workspace_id, + 'name' => 'Beta Tenant', + ]); + createUserWithTenant(tenant: $tenantB, user: $user, role: 'owner'); + + $reviewA = composeTenantReviewForTest($tenantA, $user); + $reviewB = composeTenantReviewForTest($tenantB, $user); + + $this->actingAs($user); + setAdminPanelContext(); + session()->put(WorkspaceContext::SESSION_KEY, (int) $tenantA->workspace_id); + session()->put(WorkspaceContext::LAST_TENANT_IDS_SESSION_KEY, [ + (string) $tenantA->workspace_id => (int) $tenantA->getKey(), + ]); + + $component = Livewire::actingAs($user) + ->test(ReviewRegister::class) + ->assertActionVisible('clear_filters') + ->assertCanSeeTableRecords([$reviewA]) + ->assertCanNotSeeTableRecords([$reviewB]); + + expect(app(WorkspaceContext::class)->lastTenantId())->toBe((int) $tenantA->getKey()); + + $component + ->callAction('clear_filters') + ->assertActionHidden('clear_filters') + ->assertCanSeeTableRecords([$reviewA, $reviewB]); + + expect(app(WorkspaceContext::class)->lastTenantId())->toBeNull(); +}); + it('keeps stale and partial review rows aligned with tenant review detail trust', function (): void { $staleTenant = Tenant::factory()->create(['name' => 'Stale Tenant']); [$user, $staleTenant] = createUserWithTenant(tenant: $staleTenant, role: 'owner');