feat: cut over workspace-owned analysis shell context
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m49s
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m49s
This commit is contained in:
parent
ddf7c15c52
commit
ef9380ac32
@ -15,6 +15,7 @@
|
||||
use App\Support\Filament\CanonicalAdminEnvironmentFilterState;
|
||||
use App\Support\Filament\TablePaginationProfiles;
|
||||
use App\Support\Navigation\CanonicalNavigationContext;
|
||||
use App\Support\Navigation\WorkspaceHubEnvironmentFilter;
|
||||
use App\Support\OperateHub\OperateHubShell;
|
||||
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
|
||||
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
|
||||
@ -182,7 +183,7 @@ public function availableFilters(): array
|
||||
'options' => [],
|
||||
],
|
||||
[
|
||||
'key' => 'tenant',
|
||||
'key' => 'environment_id',
|
||||
'label' => 'ManagedEnvironment',
|
||||
'fixed' => false,
|
||||
'options' => collect($this->visibleTenants())
|
||||
@ -402,14 +403,22 @@ private function tenantFilterOptions(): array
|
||||
|
||||
private function applyRequestedTenantPrefilter(): void
|
||||
{
|
||||
$requestedTenant = request()->query('tenant');
|
||||
$workspace = $this->workspace();
|
||||
|
||||
if (! is_string($requestedTenant) && ! is_numeric($requestedTenant)) {
|
||||
if (! $workspace instanceof Workspace) {
|
||||
return;
|
||||
}
|
||||
|
||||
$filter = WorkspaceHubEnvironmentFilter::fromRequest(request(), $workspace);
|
||||
|
||||
if (! $filter instanceof WorkspaceHubEnvironmentFilter) {
|
||||
return;
|
||||
}
|
||||
|
||||
$environmentId = $filter->environmentId();
|
||||
|
||||
foreach ($this->visibleTenants() as $tenant) {
|
||||
if ((string) $tenant->getKey() !== (string) $requestedTenant && (string) $tenant->external_id !== (string) $requestedTenant) {
|
||||
if ((int) $tenant->getKey() !== $environmentId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -418,6 +427,8 @@ private function applyRequestedTenantPrefilter(): void
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new NotFoundHttpException;
|
||||
}
|
||||
|
||||
private function normalizeTenantFilterState(): void
|
||||
@ -583,9 +594,9 @@ private function navigationContext(): CanonicalNavigationContext
|
||||
|
||||
private function reportUrl(array $overrides = []): string
|
||||
{
|
||||
$resolvedTenant = array_key_exists('tenant', $overrides)
|
||||
? $overrides['tenant']
|
||||
: $this->filteredTenant()?->external_id;
|
||||
$resolvedEnvironment = array_key_exists('environment_id', $overrides)
|
||||
? $overrides['environment_id']
|
||||
: $this->filteredTenant()?->getKey();
|
||||
$resolvedReason = array_key_exists('reason', $overrides)
|
||||
? $overrides['reason']
|
||||
: $this->currentReasonFilter();
|
||||
@ -593,7 +604,7 @@ private function reportUrl(array $overrides = []): string
|
||||
return static::getUrl(
|
||||
panel: 'admin',
|
||||
parameters: array_filter([
|
||||
'tenant' => is_string($resolvedTenant) && $resolvedTenant !== '' ? $resolvedTenant : null,
|
||||
'environment_id' => is_numeric($resolvedEnvironment) ? (int) $resolvedEnvironment : null,
|
||||
'reason' => is_string($resolvedReason) && $resolvedReason !== FindingAssignmentHygieneService::FILTER_ALL
|
||||
? $resolvedReason
|
||||
: null,
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
use App\Support\Filament\CanonicalAdminEnvironmentFilterState;
|
||||
use App\Support\Filament\TablePaginationProfiles;
|
||||
use App\Support\Navigation\CanonicalNavigationContext;
|
||||
use App\Support\Navigation\WorkspaceHubEnvironmentFilter;
|
||||
use App\Support\OperateHub\OperateHubShell;
|
||||
use App\Support\Rbac\UiEnforcement;
|
||||
use App\Support\Rbac\UiTooltips;
|
||||
@ -79,7 +80,7 @@ public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
|
||||
{
|
||||
return ActionSurfaceDeclaration::forPage(ActionSurfaceProfile::ListOnlyReadOnly, ActionSurfaceType::ReadOnlyRegistryReport)
|
||||
->withListRowPrimaryActionLimit(1)
|
||||
->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions keep the shared-unassigned scope fixed and expose only a tenant-prefilter clear action when needed.')
|
||||
->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions keep the shared-unassigned scope fixed and expose only an environment-prefilter clear action when needed.')
|
||||
->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
|
||||
->exempt(ActionSurfaceSlot::ListRowMoreMenu, 'The intake queue keeps Claim finding inline and does not render a secondary More menu on rows.')
|
||||
->exempt(ActionSurfaceSlot::ListBulkMoreGroup, 'The intake queue does not expose bulk actions in v1.')
|
||||
@ -521,14 +522,22 @@ private function tenantFilterOptions(): array
|
||||
|
||||
private function applyRequestedTenantPrefilter(): void
|
||||
{
|
||||
$requestedTenant = request()->query('tenant');
|
||||
$workspace = $this->workspace();
|
||||
|
||||
if (! is_string($requestedTenant) && ! is_numeric($requestedTenant)) {
|
||||
if (! $workspace instanceof Workspace) {
|
||||
return;
|
||||
}
|
||||
|
||||
$filter = WorkspaceHubEnvironmentFilter::fromRequest(request(), $workspace);
|
||||
|
||||
if (! $filter instanceof WorkspaceHubEnvironmentFilter) {
|
||||
return;
|
||||
}
|
||||
|
||||
$environmentId = $filter->environmentId();
|
||||
|
||||
foreach ($this->visibleTenants() as $tenant) {
|
||||
if ((string) $tenant->getKey() !== (string) $requestedTenant && (string) $tenant->external_id !== (string) $requestedTenant) {
|
||||
if ((int) $tenant->getKey() !== $environmentId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -537,6 +546,8 @@ private function applyRequestedTenantPrefilter(): void
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new NotFoundHttpException;
|
||||
}
|
||||
|
||||
private function normalizeTenantFilterState(): void
|
||||
@ -720,9 +731,9 @@ private function incomingGovernanceContext(): ?CanonicalNavigationContext
|
||||
|
||||
private function queueUrl(array $overrides = []): string
|
||||
{
|
||||
$resolvedTenant = array_key_exists('tenant', $overrides)
|
||||
? $overrides['tenant']
|
||||
: $this->filteredTenant()?->external_id;
|
||||
$resolvedEnvironment = array_key_exists('environment_id', $overrides)
|
||||
? $overrides['environment_id']
|
||||
: $this->filteredTenant()?->getKey();
|
||||
$resolvedView = array_key_exists('view', $overrides)
|
||||
? $overrides['view']
|
||||
: $this->currentQueueView();
|
||||
@ -730,7 +741,7 @@ private function queueUrl(array $overrides = []): string
|
||||
return static::getUrl(
|
||||
panel: 'admin',
|
||||
parameters: array_filter([
|
||||
'tenant' => is_string($resolvedTenant) && $resolvedTenant !== '' ? $resolvedTenant : null,
|
||||
'environment_id' => is_numeric($resolvedEnvironment) ? (int) $resolvedEnvironment : null,
|
||||
'view' => $resolvedView === 'needs_triage' ? 'needs_triage' : null,
|
||||
], static fn (mixed $value): bool => $value !== null && $value !== ''),
|
||||
);
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
use App\Support\Filament\CanonicalAdminEnvironmentFilterState;
|
||||
use App\Support\Filament\TablePaginationProfiles;
|
||||
use App\Support\Navigation\CanonicalNavigationContext;
|
||||
use App\Support\Navigation\WorkspaceHubEnvironmentFilter;
|
||||
use App\Support\OperateHub\OperateHubShell;
|
||||
use App\Support\Ui\ActionSurface\ActionSurfaceDeclaration;
|
||||
use App\Support\Ui\ActionSurface\Enums\ActionSurfaceInspectAffordance;
|
||||
@ -206,7 +207,7 @@ public function availableFilters(): array
|
||||
'options' => [],
|
||||
],
|
||||
[
|
||||
'key' => 'tenant',
|
||||
'key' => 'environment_id',
|
||||
'label' => 'Managed environment',
|
||||
'fixed' => false,
|
||||
'options' => collect($this->visibleTenants())
|
||||
@ -461,14 +462,22 @@ private function tenantFilterOptions(): array
|
||||
|
||||
private function applyRequestedTenantPrefilter(): void
|
||||
{
|
||||
$requestedTenant = request()->query('tenant');
|
||||
$workspace = $this->workspace();
|
||||
|
||||
if (! is_string($requestedTenant) && ! is_numeric($requestedTenant)) {
|
||||
if (! $workspace instanceof Workspace) {
|
||||
return;
|
||||
}
|
||||
|
||||
$filter = WorkspaceHubEnvironmentFilter::fromRequest(request(), $workspace);
|
||||
|
||||
if (! $filter instanceof WorkspaceHubEnvironmentFilter) {
|
||||
return;
|
||||
}
|
||||
|
||||
$environmentId = $filter->environmentId();
|
||||
|
||||
foreach ($this->visibleTenants() as $tenant) {
|
||||
if ((string) $tenant->getKey() !== (string) $requestedTenant && (string) $tenant->external_id !== (string) $requestedTenant) {
|
||||
if ((int) $tenant->getKey() !== $environmentId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -477,6 +486,8 @@ private function applyRequestedTenantPrefilter(): void
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
throw new NotFoundHttpException;
|
||||
}
|
||||
|
||||
private function normalizeTenantFilterState(): void
|
||||
@ -667,8 +678,8 @@ private function queueUrl(): string
|
||||
return static::getUrl(
|
||||
panel: 'admin',
|
||||
parameters: array_filter([
|
||||
'tenant' => $tenant?->external_id,
|
||||
], static fn (mixed $value): bool => $value !== null && $value !== ''),
|
||||
'environment_id' => $tenant?->getKey(),
|
||||
], static fn (mixed $value): bool => is_numeric($value)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -307,8 +307,8 @@ private function assignedFindingsSection(
|
||||
MyFindingsInbox::getUrl(
|
||||
panel: 'admin',
|
||||
parameters: array_filter([
|
||||
'tenant' => $selectedTenant?->external_id,
|
||||
], static fn (mixed $value): bool => is_string($value) && $value !== ''),
|
||||
'environment_id' => $selectedTenant?->getKey(),
|
||||
], static fn (mixed $value): bool => is_numeric($value)),
|
||||
),
|
||||
$navigationContext?->toQuery() ?? [],
|
||||
),
|
||||
@ -349,7 +349,7 @@ private function intakeFindingsSection(
|
||||
FindingsIntakeQueue::getUrl(
|
||||
panel: 'admin',
|
||||
parameters: array_filter([
|
||||
'tenant' => $selectedTenant?->external_id,
|
||||
'environment_id' => $selectedTenant?->getKey(),
|
||||
'view' => $needsTriageCount > 0 ? 'needs_triage' : null,
|
||||
], static fn (mixed $value): bool => $value !== null && $value !== ''),
|
||||
),
|
||||
|
||||
@ -10,6 +10,7 @@
|
||||
enum AdminSurfaceScope: string
|
||||
{
|
||||
case WorkspaceWideSurface = 'workspace_wide_surface';
|
||||
case WorkspaceOwnedAnalysisSurface = 'workspace_owned_analysis_surface';
|
||||
case WorkspaceScoped = 'workspace_scoped';
|
||||
case WorkspaceChooserException = 'workspace_chooser_exception';
|
||||
case EnvironmentBound = 'environment_bound';
|
||||
@ -42,6 +43,10 @@ public static function fromPath(string $path): self
|
||||
return self::WorkspaceWideSurface;
|
||||
}
|
||||
|
||||
if (self::isWorkspaceOwnedAnalysisSurfacePath($normalizedPath)) {
|
||||
return self::WorkspaceOwnedAnalysisSurface;
|
||||
}
|
||||
|
||||
if (
|
||||
str_starts_with($normalizedPath, '/admin/evidence/')
|
||||
&& ! str_starts_with($normalizedPath, '/admin/evidence/overview')
|
||||
@ -80,6 +85,7 @@ public function allowsEnvironmentlessState(): bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::WorkspaceWideSurface,
|
||||
self::WorkspaceOwnedAnalysisSurface,
|
||||
self::WorkspaceScoped,
|
||||
self::WorkspaceChooserException,
|
||||
self::OnboardingWorkflow,
|
||||
@ -92,6 +98,7 @@ public function forcesEnvironmentlessShellContext(): bool
|
||||
{
|
||||
return match ($this) {
|
||||
self::WorkspaceWideSurface,
|
||||
self::WorkspaceOwnedAnalysisSurface,
|
||||
self::WorkspaceChooserException,
|
||||
self::CanonicalWorkspaceRecordViewer => true,
|
||||
default => false,
|
||||
@ -116,6 +123,13 @@ private static function isWorkspaceWideSurfacePath(string $normalizedPath): bool
|
||||
return WorkspaceHubRegistry::isWorkspaceHubPath($normalizedPath);
|
||||
}
|
||||
|
||||
private static function isWorkspaceOwnedAnalysisSurfacePath(string $normalizedPath): bool
|
||||
{
|
||||
return preg_match('#^/admin/(baseline-profiles|baseline-snapshots)(?:/.*)?$#', $normalizedPath) === 1
|
||||
|| preg_match('#^/admin/findings/(?:my-work|intake|hygiene)/?$#', $normalizedPath) === 1
|
||||
|| preg_match('#^/admin/cross-environment-compare/?$#', $normalizedPath) === 1;
|
||||
}
|
||||
|
||||
private static function effectivePath(Request $request): string
|
||||
{
|
||||
$path = '/'.ltrim((string) $request->path(), '/');
|
||||
|
||||
@ -21,6 +21,7 @@ public static function fromSurfaceScope(AdminSurfaceScope $pageCategory): self
|
||||
AdminSurfaceScope::EnvironmentScopedEvidence => self::AdministrativeManagement,
|
||||
AdminSurfaceScope::CanonicalWorkspaceRecordViewer => self::CanonicalWorkspaceRecord,
|
||||
AdminSurfaceScope::WorkspaceWideSurface,
|
||||
AdminSurfaceScope::WorkspaceOwnedAnalysisSurface,
|
||||
AdminSurfaceScope::WorkspaceScoped,
|
||||
AdminSurfaceScope::WorkspaceChooserException => self::StandardActiveOperating,
|
||||
};
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
</h1>
|
||||
|
||||
<p class="max-w-3xl text-sm leading-6 text-gray-600 dark:text-gray-300">
|
||||
Review visible broken assignments and stale in-progress work across entitled tenants in one read-first repair queue. Existing finding detail stays the only place where reassignment or lifecycle repair happens.
|
||||
Review visible broken assignments and stale in-progress work across entitled environments in one read-first repair queue. Existing finding detail stays the only place where reassignment or lifecycle repair happens.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -68,14 +68,11 @@
|
||||
{{ $scope['reason_filter_label'] }}
|
||||
</div>
|
||||
<div class="mt-1 text-sm text-gray-600 dark:text-gray-300">
|
||||
@if (($scope['tenant_prefilter_source'] ?? 'none') === 'active_tenant_context')
|
||||
ManagedEnvironment prefilter from active context:
|
||||
<span class="font-medium text-gray-950 dark:text-white">{{ $scope['tenant_label'] }}</span>
|
||||
@elseif (($scope['tenant_prefilter_source'] ?? 'none') === 'explicit_filter')
|
||||
ManagedEnvironment filter applied:
|
||||
@if (($scope['tenant_prefilter_source'] ?? 'none') === 'explicit_filter')
|
||||
Environment filter applied:
|
||||
<span class="font-medium text-gray-950 dark:text-white">{{ $scope['tenant_label'] }}</span>
|
||||
@else
|
||||
All visible tenants are currently included.
|
||||
All visible environments are currently included.
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
</h1>
|
||||
|
||||
<p class="max-w-3xl text-sm leading-6 text-gray-600 dark:text-gray-300">
|
||||
Review visible unassigned open findings across entitled tenants in one queue. ManagedEnvironment context can narrow the view, but the intake scope stays fixed.
|
||||
Review visible unassigned open findings across entitled environments in one queue. An explicit environment filter can narrow the view, but the intake scope stays fixed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -68,14 +68,11 @@
|
||||
{{ $scope['queue_view_label'] }}
|
||||
</div>
|
||||
<div class="mt-1 text-sm text-gray-600 dark:text-gray-300">
|
||||
@if (($scope['tenant_prefilter_source'] ?? 'none') === 'active_tenant_context')
|
||||
ManagedEnvironment prefilter from active context:
|
||||
<span class="font-medium text-gray-950 dark:text-white">{{ $scope['tenant_label'] }}</span>
|
||||
@elseif (($scope['tenant_prefilter_source'] ?? 'none') === 'explicit_filter')
|
||||
ManagedEnvironment filter applied:
|
||||
@if (($scope['tenant_prefilter_source'] ?? 'none') === 'explicit_filter')
|
||||
Environment filter applied:
|
||||
<span class="font-medium text-gray-950 dark:text-white">{{ $scope['tenant_label'] }}</span>
|
||||
@else
|
||||
All visible tenants are currently included.
|
||||
All visible environments are currently included.
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -18,7 +18,7 @@
|
||||
</h1>
|
||||
|
||||
<p class="max-w-3xl text-sm leading-6 text-gray-600 dark:text-gray-300">
|
||||
Review open assigned findings across visible tenants in one queue. ManagedEnvironment context can narrow the view, but the personal assignment scope stays fixed.
|
||||
Review open assigned findings across visible environments in one queue. An explicit environment filter can narrow the view, but the personal assignment scope stays fixed.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -56,14 +56,11 @@
|
||||
Assigned to me only
|
||||
</div>
|
||||
<div class="mt-1 text-sm text-gray-600 dark:text-gray-300">
|
||||
@if (($scope['tenant_prefilter_source'] ?? 'none') === 'active_tenant_context')
|
||||
ManagedEnvironment prefilter from active context:
|
||||
<span class="font-medium text-gray-950 dark:text-white">{{ $scope['tenant_label'] }}</span>
|
||||
@elseif (($scope['tenant_prefilter_source'] ?? 'none') === 'explicit_filter')
|
||||
ManagedEnvironment filter applied:
|
||||
@if (($scope['tenant_prefilter_source'] ?? 'none') === 'explicit_filter')
|
||||
Environment filter applied:
|
||||
<span class="font-medium text-gray-950 dark:text-white">{{ $scope['tenant_label'] }}</span>
|
||||
@else
|
||||
All visible tenants are currently included.
|
||||
All visible environments are currently included.
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -43,11 +43,13 @@ function findingsHygienePage(?User $user = null, array $query = [])
|
||||
|
||||
setAdminPanelContext();
|
||||
|
||||
$factory = $query === []
|
||||
? Livewire::actingAs(auth()->user())
|
||||
: Livewire::withQueryParams($query)->actingAs(auth()->user());
|
||||
$factory = Livewire::withHeaders(['referer' => FindingsHygieneReport::getUrl(panel: 'admin')]);
|
||||
|
||||
return $factory->test(FindingsHygieneReport::class);
|
||||
if ($query !== []) {
|
||||
$factory = $factory->withQueryParams($query);
|
||||
}
|
||||
|
||||
return $factory->actingAs(auth()->user())->test(FindingsHygieneReport::class);
|
||||
}
|
||||
|
||||
function makeFindingsHygieneFinding(ManagedEnvironment $tenant, array $attributes = []): Finding
|
||||
@ -256,7 +258,7 @@ function recordFindingsHygieneAudit(Finding $finding, string $action, CarbonImmu
|
||||
'options' => [],
|
||||
],
|
||||
[
|
||||
'key' => 'tenant',
|
||||
'key' => 'environment_id',
|
||||
'label' => 'ManagedEnvironment',
|
||||
'fixed' => false,
|
||||
'options' => [
|
||||
@ -351,7 +353,68 @@ function recordFindingsHygieneAudit(Finding $finding, string $action, CarbonImmu
|
||||
]);
|
||||
});
|
||||
|
||||
it('explains when the active tenant prefilter hides otherwise visible hygiene issues and clears it in place', function (): void {
|
||||
it('ignores remembered environments and retired tenant query aliases on the workspace-owned hygiene surface', function (): void {
|
||||
[$user, $tenantA] = findingsHygieneActingUser();
|
||||
|
||||
$tenantB = ManagedEnvironment::factory()->create([
|
||||
'status' => 'active',
|
||||
'workspace_id' => (int) $tenantA->workspace_id,
|
||||
'name' => 'Beta ManagedEnvironment',
|
||||
]);
|
||||
createUserWithTenant($tenantB, $user, role: 'readonly', workspaceRole: 'readonly');
|
||||
|
||||
$lostMemberA = User::factory()->create(['name' => 'Lost Member A']);
|
||||
createUserWithTenant($tenantA, $lostMemberA, role: 'readonly', workspaceRole: 'readonly');
|
||||
ManagedEnvironmentMembership::query()
|
||||
->where('managed_environment_id', (int) $tenantA->getKey())
|
||||
->where('user_id', (int) $lostMemberA->getKey())
|
||||
->delete();
|
||||
|
||||
$lostMemberB = User::factory()->create(['name' => 'Lost Member B']);
|
||||
createUserWithTenant($tenantB, $lostMemberB, role: 'readonly', workspaceRole: 'readonly');
|
||||
ManagedEnvironmentMembership::query()
|
||||
->where('managed_environment_id', (int) $tenantB->getKey())
|
||||
->where('user_id', (int) $lostMemberB->getKey())
|
||||
->delete();
|
||||
|
||||
$tenantAIssue = makeFindingsHygieneFinding($tenantA, [
|
||||
'owner_user_id' => (int) $user->getKey(),
|
||||
'assignee_user_id' => (int) $lostMemberA->getKey(),
|
||||
'subject_display_name' => 'ManagedEnvironment A Issue',
|
||||
]);
|
||||
$tenantBIssue = makeFindingsHygieneFinding($tenantB, [
|
||||
'owner_user_id' => (int) $user->getKey(),
|
||||
'assignee_user_id' => (int) $lostMemberB->getKey(),
|
||||
'subject_display_name' => 'ManagedEnvironment B Issue',
|
||||
]);
|
||||
|
||||
session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
|
||||
(string) $tenantA->workspace_id => (int) $tenantB->getKey(),
|
||||
]);
|
||||
|
||||
$component = findingsHygienePage($user, [
|
||||
'tenant' => (string) $tenantB->external_id,
|
||||
'tenant_id' => (int) $tenantB->getKey(),
|
||||
'managed_environment_id' => (int) $tenantB->getKey(),
|
||||
'tenant_scope' => 'environment',
|
||||
'tableFilters' => [
|
||||
'managed_environment_id' => ['value' => (string) $tenantB->getKey()],
|
||||
],
|
||||
])
|
||||
->assertSet('tableFilters.managed_environment_id.value', null)
|
||||
->assertCanSeeTableRecords([$tenantAIssue, $tenantBIssue]);
|
||||
|
||||
expect($component->instance()->appliedScope())->toBe([
|
||||
'workspace_scoped' => true,
|
||||
'fixed_scope' => 'visible_findings_hygiene_only',
|
||||
'reason_filter' => 'all',
|
||||
'reason_filter_label' => 'All issues',
|
||||
'tenant_prefilter_source' => 'none',
|
||||
'tenant_label' => null,
|
||||
]);
|
||||
});
|
||||
|
||||
it('explains when the explicit environment_id prefilter hides otherwise visible hygiene issues and clears it in place', function (): void {
|
||||
[$user, $tenantA] = findingsHygieneActingUser();
|
||||
|
||||
$tenantB = ManagedEnvironment::factory()->create([
|
||||
@ -374,11 +437,7 @@ function recordFindingsHygieneAudit(Finding $finding, string $action, CarbonImmu
|
||||
'subject_display_name' => 'ManagedEnvironment A Issue',
|
||||
]);
|
||||
|
||||
session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
|
||||
(string) $tenantA->workspace_id => (int) $tenantB->getKey(),
|
||||
]);
|
||||
|
||||
$component = findingsHygienePage($user)
|
||||
$component = findingsHygienePage($user, ['environment_id' => (int) $tenantB->getKey()])
|
||||
->assertSet('tableFilters.managed_environment_id.value', (string) $tenantB->getKey())
|
||||
->assertCanNotSeeTableRecords([$tenantAIssue])
|
||||
->assertSee('No hygiene issues match this environment scope')
|
||||
|
||||
@ -39,15 +39,16 @@
|
||||
tenantId: (int) $tenant->getKey(),
|
||||
familyKey: 'intake_findings',
|
||||
backLinkUrl: GovernanceInbox::getUrl(panel: 'admin', parameters: [
|
||||
'managed_environment_id' => (string) $tenant->getKey(),
|
||||
'environment_id' => (int) $tenant->getKey(),
|
||||
'family' => 'intake_findings',
|
||||
]),
|
||||
);
|
||||
|
||||
Livewire::withQueryParams(array_replace($context->toQuery(), [
|
||||
'tenant' => (string) $tenant->external_id,
|
||||
'view' => 'needs_triage',
|
||||
]))
|
||||
Livewire::withHeaders(['referer' => FindingsIntakeQueue::getUrl(panel: 'admin')])
|
||||
->withQueryParams(array_replace($context->toQuery(), [
|
||||
'environment_id' => (int) $tenant->getKey(),
|
||||
'view' => 'needs_triage',
|
||||
]))
|
||||
->actingAs($user)
|
||||
->test(FindingsIntakeQueue::class)
|
||||
->assertSet('tableFilters.managed_environment_id.value', (string) $tenant->getKey())
|
||||
|
||||
@ -31,11 +31,13 @@ function findingsIntakePage(?User $user = null, array $query = [])
|
||||
|
||||
setAdminPanelContext();
|
||||
|
||||
$factory = $query === []
|
||||
? Livewire::actingAs(auth()->user())
|
||||
: Livewire::withQueryParams($query)->actingAs(auth()->user());
|
||||
$factory = Livewire::withHeaders(['referer' => FindingsIntakeQueue::getUrl(panel: 'admin')]);
|
||||
|
||||
return $factory->test(FindingsIntakeQueue::class);
|
||||
if ($query !== []) {
|
||||
$factory = $factory->withQueryParams($query);
|
||||
}
|
||||
|
||||
return $factory->actingAs(auth()->user())->test(FindingsIntakeQueue::class);
|
||||
}
|
||||
|
||||
function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []): Finding
|
||||
@ -140,7 +142,7 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []):
|
||||
->and($queueViews['needs_triage']['active'])->toBeFalse();
|
||||
});
|
||||
|
||||
it('defaults to the active tenant prefilter and lets the operator clear it without dropping intake scope', function (): void {
|
||||
it('applies the explicit environment_id prefilter and lets the operator clear it without dropping intake scope', function (): void {
|
||||
[$user, $tenantA] = findingsIntakeActingUser();
|
||||
|
||||
$tenantB = ManagedEnvironment::factory()->create([
|
||||
@ -159,11 +161,7 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []):
|
||||
'status' => Finding::STATUS_TRIAGED,
|
||||
]);
|
||||
|
||||
session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
|
||||
(string) $tenantA->workspace_id => (int) $tenantB->getKey(),
|
||||
]);
|
||||
|
||||
$component = findingsIntakePage($user)
|
||||
$component = findingsIntakePage($user, ['environment_id' => (int) $tenantB->getKey()])
|
||||
->assertSet('tableFilters.managed_environment_id.value', (string) $tenantB->getKey())
|
||||
->assertCanSeeTableRecords([$findingB])
|
||||
->assertCanNotSeeTableRecords([$findingA])
|
||||
@ -174,7 +172,7 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []):
|
||||
'fixed_scope' => 'visible_unassigned_open_findings_only',
|
||||
'queue_view' => 'unassigned',
|
||||
'queue_view_label' => 'Unassigned',
|
||||
'tenant_prefilter_source' => 'active_tenant_context',
|
||||
'tenant_prefilter_source' => 'explicit_filter',
|
||||
'tenant_label' => $tenantB->name,
|
||||
]);
|
||||
|
||||
@ -191,7 +189,7 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []):
|
||||
]);
|
||||
});
|
||||
|
||||
it('keeps the needs triage view active when clearing the tenant prefilter', function (): void {
|
||||
it('keeps the needs triage view active when clearing the environment_id prefilter', function (): void {
|
||||
[$user, $tenantA] = findingsIntakeActingUser();
|
||||
|
||||
$tenantB = ManagedEnvironment::factory()->create([
|
||||
@ -215,11 +213,10 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []):
|
||||
'status' => Finding::STATUS_TRIAGED,
|
||||
]);
|
||||
|
||||
session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
|
||||
(string) $tenantA->workspace_id => (int) $tenantB->getKey(),
|
||||
]);
|
||||
|
||||
$component = findingsIntakePage($user, ['view' => 'needs_triage'])
|
||||
$component = findingsIntakePage($user, [
|
||||
'environment_id' => (int) $tenantB->getKey(),
|
||||
'view' => 'needs_triage',
|
||||
])
|
||||
->assertSet('tableFilters.managed_environment_id.value', (string) $tenantB->getKey())
|
||||
->assertCanSeeTableRecords([$tenantBTriage])
|
||||
->assertCanNotSeeTableRecords([$tenantATriage, $tenantBBacklog]);
|
||||
@ -229,7 +226,7 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []):
|
||||
'fixed_scope' => 'visible_unassigned_open_findings_only',
|
||||
'queue_view' => 'needs_triage',
|
||||
'queue_view_label' => 'Needs triage',
|
||||
'tenant_prefilter_source' => 'active_tenant_context',
|
||||
'tenant_prefilter_source' => 'explicit_filter',
|
||||
'tenant_label' => $tenantB->name,
|
||||
]);
|
||||
|
||||
@ -252,6 +249,51 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []):
|
||||
->and($queueViews['needs_triage']['active'])->toBeTrue();
|
||||
});
|
||||
|
||||
it('ignores remembered environments and retired tenant query aliases on the workspace-owned intake surface', function (): void {
|
||||
[$user, $tenantA] = findingsIntakeActingUser();
|
||||
|
||||
$tenantB = ManagedEnvironment::factory()->create([
|
||||
'status' => 'active',
|
||||
'workspace_id' => (int) $tenantA->workspace_id,
|
||||
'name' => 'Beta ManagedEnvironment',
|
||||
]);
|
||||
createUserWithTenant($tenantB, $user, role: 'readonly', workspaceRole: 'readonly');
|
||||
|
||||
$findingA = makeIntakeFinding($tenantA, [
|
||||
'subject_external_id' => 'tenant-a',
|
||||
'status' => Finding::STATUS_NEW,
|
||||
]);
|
||||
$findingB = makeIntakeFinding($tenantB, [
|
||||
'subject_external_id' => 'tenant-b',
|
||||
'status' => Finding::STATUS_TRIAGED,
|
||||
]);
|
||||
|
||||
session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
|
||||
(string) $tenantA->workspace_id => (int) $tenantB->getKey(),
|
||||
]);
|
||||
|
||||
$component = findingsIntakePage($user, [
|
||||
'tenant' => (string) $tenantB->external_id,
|
||||
'tenant_id' => (int) $tenantB->getKey(),
|
||||
'managed_environment_id' => (int) $tenantB->getKey(),
|
||||
'tenant_scope' => 'environment',
|
||||
'tableFilters' => [
|
||||
'managed_environment_id' => ['value' => (string) $tenantB->getKey()],
|
||||
],
|
||||
])
|
||||
->assertSet('tableFilters.managed_environment_id.value', null)
|
||||
->assertCanSeeTableRecords([$findingA, $findingB]);
|
||||
|
||||
expect($component->instance()->appliedScope())->toBe([
|
||||
'workspace_scoped' => true,
|
||||
'fixed_scope' => 'visible_unassigned_open_findings_only',
|
||||
'queue_view' => 'unassigned',
|
||||
'queue_view_label' => 'Unassigned',
|
||||
'tenant_prefilter_source' => 'none',
|
||||
'tenant_label' => null,
|
||||
]);
|
||||
});
|
||||
|
||||
it('separates needs triage from the remaining backlog and keeps deterministic urgency ordering', function (): void {
|
||||
[$user, $tenant] = findingsIntakeActingUser();
|
||||
|
||||
@ -303,7 +345,7 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []):
|
||||
]);
|
||||
|
||||
$component = findingsIntakePage($user, [
|
||||
'tenant' => (string) $tenant->external_id,
|
||||
'environment_id' => (int) $tenant->getKey(),
|
||||
'view' => 'needs_triage',
|
||||
]);
|
||||
|
||||
@ -333,9 +375,7 @@ function makeIntakeFinding(ManagedEnvironment $tenant, array $attributes = []):
|
||||
'subject_external_id' => 'available-elsewhere',
|
||||
]);
|
||||
|
||||
findingsIntakePage($user, [
|
||||
'tenant' => (string) $tenantA->external_id,
|
||||
])
|
||||
findingsIntakePage($user, ['environment_id' => (int) $tenantA->getKey()])
|
||||
->assertSee('No intake findings match this environment scope')
|
||||
->assertTableEmptyStateActionsExistInOrder(['clear_tenant_filter_empty']);
|
||||
|
||||
|
||||
@ -38,14 +38,15 @@
|
||||
tenantId: (int) $tenant->getKey(),
|
||||
familyKey: 'assigned_findings',
|
||||
backLinkUrl: GovernanceInbox::getUrl(panel: 'admin', parameters: [
|
||||
'managed_environment_id' => (string) $tenant->getKey(),
|
||||
'environment_id' => (int) $tenant->getKey(),
|
||||
'family' => 'assigned_findings',
|
||||
]),
|
||||
);
|
||||
|
||||
Livewire::withQueryParams(array_replace($context->toQuery(), [
|
||||
'tenant' => (string) $tenant->external_id,
|
||||
]))
|
||||
Livewire::withHeaders(['referer' => MyFindingsInbox::getUrl(panel: 'admin')])
|
||||
->withQueryParams(array_replace($context->toQuery(), [
|
||||
'environment_id' => (int) $tenant->getKey(),
|
||||
]))
|
||||
->actingAs($user)
|
||||
->test(MyFindingsInbox::class)
|
||||
->assertSet('tableFilters.managed_environment_id.value', (string) $tenant->getKey())
|
||||
|
||||
@ -31,9 +31,13 @@ function myWorkInboxPage(?User $user = null, array $query = [])
|
||||
|
||||
setAdminPanelContext();
|
||||
|
||||
$factory = $query === [] ? Livewire::actingAs(auth()->user()) : Livewire::withQueryParams($query)->actingAs(auth()->user());
|
||||
$factory = Livewire::withHeaders(['referer' => MyFindingsInbox::getUrl(panel: 'admin')]);
|
||||
|
||||
return $factory->test(MyFindingsInbox::class);
|
||||
if ($query !== []) {
|
||||
$factory = $factory->withQueryParams($query);
|
||||
}
|
||||
|
||||
return $factory->actingAs(auth()->user())->test(MyFindingsInbox::class);
|
||||
}
|
||||
|
||||
function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee, array $attributes = []): Finding
|
||||
@ -121,7 +125,7 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee,
|
||||
'options' => [],
|
||||
],
|
||||
[
|
||||
'key' => 'tenant',
|
||||
'key' => 'environment_id',
|
||||
'label' => 'Managed environment',
|
||||
'fixed' => false,
|
||||
'options' => [
|
||||
@ -150,7 +154,50 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee,
|
||||
]);
|
||||
});
|
||||
|
||||
it('defaults to the active environment prefilter and lets the operator clear it without dropping personal scope', function (): void {
|
||||
it('applies the explicit environment_id prefilter and lets the operator clear it without dropping personal scope', function (): void {
|
||||
[$user, $tenantA] = myWorkInboxActingUser();
|
||||
|
||||
$tenantB = ManagedEnvironment::factory()->create([
|
||||
'status' => 'active',
|
||||
'workspace_id' => (int) $tenantA->workspace_id,
|
||||
'name' => 'Beta ManagedEnvironment',
|
||||
]);
|
||||
createUserWithTenant($tenantB, $user, role: 'readonly', workspaceRole: 'readonly');
|
||||
|
||||
$findingA = makeAssignedFindingForInbox($tenantA, $user, [
|
||||
'subject_external_id' => 'tenant-a',
|
||||
'status' => Finding::STATUS_NEW,
|
||||
]);
|
||||
$findingB = makeAssignedFindingForInbox($tenantB, $user, [
|
||||
'subject_external_id' => 'tenant-b',
|
||||
'status' => Finding::STATUS_TRIAGED,
|
||||
]);
|
||||
|
||||
$component = myWorkInboxPage($user, ['environment_id' => (int) $tenantB->getKey()])
|
||||
->assertSet('tableFilters.managed_environment_id.value', (string) $tenantB->getKey())
|
||||
->assertCanSeeTableRecords([$findingB])
|
||||
->assertCanNotSeeTableRecords([$findingA])
|
||||
->assertActionVisible('clear_tenant_filter');
|
||||
|
||||
expect($component->instance()->appliedScope())->toBe([
|
||||
'workspace_scoped' => true,
|
||||
'assignee_scope' => 'current_user_only',
|
||||
'tenant_prefilter_source' => 'explicit_filter',
|
||||
'tenant_label' => $tenantB->name,
|
||||
]);
|
||||
|
||||
$component->callAction('clear_tenant_filter')
|
||||
->assertCanSeeTableRecords([$findingA, $findingB]);
|
||||
|
||||
expect($component->instance()->appliedScope())->toBe([
|
||||
'workspace_scoped' => true,
|
||||
'assignee_scope' => 'current_user_only',
|
||||
'tenant_prefilter_source' => 'none',
|
||||
'tenant_label' => null,
|
||||
]);
|
||||
});
|
||||
|
||||
it('ignores remembered environments and retired tenant query aliases on the workspace-owned analysis surface', function (): void {
|
||||
[$user, $tenantA] = myWorkInboxActingUser();
|
||||
|
||||
$tenantB = ManagedEnvironment::factory()->create([
|
||||
@ -173,20 +220,16 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee,
|
||||
(string) $tenantA->workspace_id => (int) $tenantB->getKey(),
|
||||
]);
|
||||
|
||||
$component = myWorkInboxPage($user)
|
||||
->assertSet('tableFilters.managed_environment_id.value', (string) $tenantB->getKey())
|
||||
->assertCanSeeTableRecords([$findingB])
|
||||
->assertCanNotSeeTableRecords([$findingA])
|
||||
->assertActionVisible('clear_tenant_filter');
|
||||
|
||||
expect($component->instance()->appliedScope())->toBe([
|
||||
'workspace_scoped' => true,
|
||||
'assignee_scope' => 'current_user_only',
|
||||
'tenant_prefilter_source' => 'active_tenant_context',
|
||||
'tenant_label' => $tenantB->name,
|
||||
]);
|
||||
|
||||
$component->callAction('clear_tenant_filter')
|
||||
$component = myWorkInboxPage($user, [
|
||||
'tenant' => (string) $tenantB->external_id,
|
||||
'tenant_id' => (int) $tenantB->getKey(),
|
||||
'managed_environment_id' => (int) $tenantB->getKey(),
|
||||
'tenant_scope' => 'environment',
|
||||
'tableFilters' => [
|
||||
'managed_environment_id' => ['value' => (string) $tenantB->getKey()],
|
||||
],
|
||||
])
|
||||
->assertSet('tableFilters.managed_environment_id.value', null)
|
||||
->assertCanSeeTableRecords([$findingA, $findingB]);
|
||||
|
||||
expect($component->instance()->appliedScope())->toBe([
|
||||
@ -282,9 +325,7 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee,
|
||||
'subject_external_id' => 'available-elsewhere',
|
||||
]);
|
||||
|
||||
$component = myWorkInboxPage($user, [
|
||||
'tenant' => (string) $tenantA->external_id,
|
||||
])
|
||||
$component = myWorkInboxPage($user, ['environment_id' => (int) $tenantA->getKey()])
|
||||
->assertCanNotSeeTableRecords([])
|
||||
->assertSee('No assigned findings match this environment scope')
|
||||
->assertTableEmptyStateActionsExistInOrder(['clear_tenant_filter_empty']);
|
||||
@ -306,7 +347,7 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee,
|
||||
->assertTableEmptyStateActionsExistInOrder(['choose_environment_empty']);
|
||||
});
|
||||
|
||||
it('uses the active visible environment for the calm empty-state drillback when environment context exists', function (): void {
|
||||
it('keeps the calm empty-state drillback workspace-owned when remembered environment context exists', function (): void {
|
||||
[$user, $tenant] = myWorkInboxActingUser();
|
||||
|
||||
session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
|
||||
@ -315,13 +356,13 @@ function makeAssignedFindingForInbox(ManagedEnvironment $tenant, User $assignee,
|
||||
|
||||
$component = myWorkInboxPage($user)
|
||||
->assertSee('No visible assigned findings right now')
|
||||
->assertTableEmptyStateActionsExistInOrder(['open_tenant_findings_empty']);
|
||||
->assertTableEmptyStateActionsExistInOrder(['choose_environment_empty']);
|
||||
|
||||
expect($component->instance()->emptyState())->toMatchArray([
|
||||
'action_name' => 'open_tenant_findings_empty',
|
||||
'action_label' => 'Open environment findings',
|
||||
'action_name' => 'choose_environment_empty',
|
||||
'action_label' => 'Choose an environment',
|
||||
'action_kind' => 'url',
|
||||
'action_url' => FindingResource::getUrl('index', panel: 'admin', tenant: $tenant),
|
||||
'action_url' => route('filament.admin.pages.choose-environment'),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@ -44,6 +44,13 @@
|
||||
->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/workspaces/1/environments/2/baseline-compare'))->toBeFalse()
|
||||
->and(WorkspaceHubRegistry::isExplicitlyExcludedPath('/admin/workspaces/1/environments/2/baseline-compare'))->toBeTrue()
|
||||
->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/baseline-compare-landing'))->toBeFalse()
|
||||
->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/baseline-profiles'))->toBeFalse()
|
||||
->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/baseline-profiles/42/compare-matrix'))->toBeFalse()
|
||||
->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/baseline-snapshots'))->toBeFalse()
|
||||
->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/findings/my-work'))->toBeFalse()
|
||||
->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/findings/intake'))->toBeFalse()
|
||||
->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/findings/hygiene'))->toBeFalse()
|
||||
->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/cross-environment-compare'))->toBeFalse()
|
||||
->and(WorkspaceHubRegistry::isWorkspaceHubPath('/admin/workspaces/1/environments/2/stored-reports'))->toBeFalse()
|
||||
->and(WorkspaceHubRegistry::isExplicitlyExcludedPath('/admin/workspaces/1/environments/2/stored-reports'))->toBeTrue();
|
||||
});
|
||||
|
||||
@ -83,6 +83,81 @@
|
||||
->and($resolved->recoveryReason)->toBeNull();
|
||||
});
|
||||
|
||||
it('keeps workspace owned analysis surfaces tenantless when a remembered environment exists', function (string $path): void {
|
||||
$rememberedEnvironment = ManagedEnvironment::factory()->active()->create(['name' => 'Remembered ManagedEnvironment']);
|
||||
[$user, $rememberedEnvironment] = createUserWithTenant(tenant: $rememberedEnvironment, role: 'owner');
|
||||
|
||||
$this->actingAs($user);
|
||||
Filament::setTenant(null, true);
|
||||
|
||||
$workspaceId = (int) $rememberedEnvironment->workspace_id;
|
||||
|
||||
session()->put(WorkspaceContext::SESSION_KEY, $workspaceId);
|
||||
session()->put(WorkspaceContext::LAST_ENVIRONMENT_IDS_SESSION_KEY, [
|
||||
(string) $workspaceId => (int) $rememberedEnvironment->getKey(),
|
||||
]);
|
||||
|
||||
$request = Request::create($path);
|
||||
$request->setLaravelSession(app('session.store'));
|
||||
$request->setUserResolver(static fn () => $user);
|
||||
|
||||
$resolved = app(OperateHubShell::class)->resolvedContext($request);
|
||||
|
||||
expect($resolved->workspace?->getKey())->toBe($workspaceId)
|
||||
->and($resolved->tenant)->toBeNull()
|
||||
->and($resolved->tenantSource)->toBe('none')
|
||||
->and($resolved->state)->toBe('tenantless_workspace');
|
||||
})->with([
|
||||
'baseline profiles list' => ['/admin/baseline-profiles'],
|
||||
'baseline profiles detail' => ['/admin/baseline-profiles/42'],
|
||||
'baseline profiles edit' => ['/admin/baseline-profiles/42/edit'],
|
||||
'baseline profiles compare matrix' => ['/admin/baseline-profiles/42/compare-matrix'],
|
||||
'baseline snapshots list' => ['/admin/baseline-snapshots'],
|
||||
'baseline snapshots detail' => ['/admin/baseline-snapshots/42'],
|
||||
'my findings' => ['/admin/findings/my-work'],
|
||||
'findings intake' => ['/admin/findings/intake'],
|
||||
'findings hygiene' => ['/admin/findings/hygiene'],
|
||||
'cross-environment compare' => ['/admin/cross-environment-compare'],
|
||||
]);
|
||||
|
||||
it('does not resolve explicit environment_id query hints as shell tenant context on workspace owned analysis surfaces', function (string $path): void {
|
||||
$workspaceTenant = ManagedEnvironment::factory()->active()->create(['name' => 'Workspace ManagedEnvironment']);
|
||||
[$user, $workspaceTenant] = createUserWithTenant(tenant: $workspaceTenant, role: 'owner');
|
||||
|
||||
$hintedTenant = ManagedEnvironment::factory()->active()->create([
|
||||
'workspace_id' => (int) $workspaceTenant->workspace_id,
|
||||
'name' => 'Hinted ManagedEnvironment',
|
||||
]);
|
||||
createUserWithTenant(tenant: $hintedTenant, user: $user, role: 'owner');
|
||||
|
||||
$this->actingAs($user);
|
||||
Filament::setTenant(null, true);
|
||||
|
||||
$workspaceId = (int) $workspaceTenant->workspace_id;
|
||||
|
||||
session()->put(WorkspaceContext::SESSION_KEY, $workspaceId);
|
||||
|
||||
$request = Request::create($path, parameters: [
|
||||
'environment_id' => (int) $hintedTenant->getKey(),
|
||||
]);
|
||||
$request->setLaravelSession(app('session.store'));
|
||||
$request->setUserResolver(static fn () => $user);
|
||||
|
||||
$resolved = app(OperateHubShell::class)->resolvedContext($request);
|
||||
|
||||
expect($resolved->workspace?->getKey())->toBe($workspaceId)
|
||||
->and($resolved->tenant)->toBeNull()
|
||||
->and($resolved->tenantSource)->toBe('none')
|
||||
->and($resolved->state)->toBe('tenantless_workspace');
|
||||
})->with([
|
||||
'baseline profiles' => ['/admin/baseline-profiles'],
|
||||
'baseline snapshots' => ['/admin/baseline-snapshots'],
|
||||
'my findings' => ['/admin/findings/my-work'],
|
||||
'findings intake' => ['/admin/findings/intake'],
|
||||
'findings hygiene' => ['/admin/findings/hygiene'],
|
||||
'cross-environment compare' => ['/admin/cross-environment-compare'],
|
||||
]);
|
||||
|
||||
it('uses the routed tenant workspace when the tenant panel is entered without a selected workspace session', function (): void {
|
||||
$tenant = ManagedEnvironment::factory()->active()->create(['name' => 'ManagedEnvironment Panel Scope']);
|
||||
[$user, $tenant] = createUserWithTenant(tenant: $tenant, role: 'owner');
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Support\Navigation\AdminSurfaceScope;
|
||||
use App\Support\Tenants\TenantInteractionLane;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
@ -17,6 +18,16 @@
|
||||
'retired tenant panel route' => ['/admin/t/tenant-123', AdminSurfaceScope::WorkspaceScoped],
|
||||
'workspace environment detail' => ['/admin/workspaces/acme/environments/tenant-123', AdminSurfaceScope::EnvironmentBound],
|
||||
'baseline compare environment route' => ['/admin/workspaces/acme/environments/tenant-123/baseline-compare', AdminSurfaceScope::EnvironmentBound],
|
||||
'baseline profiles list' => ['/admin/baseline-profiles', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
|
||||
'baseline profiles detail' => ['/admin/baseline-profiles/42', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
|
||||
'baseline profiles edit' => ['/admin/baseline-profiles/42/edit', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
|
||||
'baseline profiles compare matrix' => ['/admin/baseline-profiles/42/compare-matrix', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
|
||||
'baseline snapshots list' => ['/admin/baseline-snapshots', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
|
||||
'baseline snapshots detail' => ['/admin/baseline-snapshots/42', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
|
||||
'my findings inbox' => ['/admin/findings/my-work', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
|
||||
'findings intake' => ['/admin/findings/intake', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
|
||||
'findings hygiene' => ['/admin/findings/hygiene', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
|
||||
'cross-environment compare' => ['/admin/cross-environment-compare', AdminSurfaceScope::WorkspaceOwnedAnalysisSurface],
|
||||
'tenant scoped evidence detail' => ['/admin/evidence/123', AdminSurfaceScope::EnvironmentScopedEvidence],
|
||||
'evidence overview' => ['/admin/evidence/overview', AdminSurfaceScope::WorkspaceWideSurface],
|
||||
'customer review workspace' => ['/admin/reviews/workspace', AdminSurfaceScope::WorkspaceWideSurface],
|
||||
@ -31,3 +42,14 @@
|
||||
'retired operation run detail' => ['/admin/operations/44', AdminSurfaceScope::WorkspaceScoped],
|
||||
'operation run detail' => ['/admin/workspaces/acme/operations/44', AdminSurfaceScope::CanonicalWorkspaceRecordViewer],
|
||||
]);
|
||||
|
||||
it('keeps workspace owned analysis surfaces tenantless without query hint or remembered environment restore', function (): void {
|
||||
$surface = AdminSurfaceScope::WorkspaceOwnedAnalysisSurface;
|
||||
|
||||
expect($surface->allowsQueryEnvironmentHints())->toBeFalse()
|
||||
->and($surface->allowsRememberedEnvironmentRestore())->toBeFalse()
|
||||
->and($surface->allowsEnvironmentlessState())->toBeTrue()
|
||||
->and($surface->forcesEnvironmentlessShellContext())->toBeTrue()
|
||||
->and($surface->requiresExplicitEnvironment())->toBeFalse()
|
||||
->and($surface->lane())->toBe(TenantInteractionLane::StandardActiveOperating);
|
||||
});
|
||||
|
||||
Loading…
Reference in New Issue
Block a user