fix: resolve post-suite state regressions
This commit is contained in:
parent
d39d6f0982
commit
4c174c717b
@ -131,7 +131,7 @@ public static function actionSurfaceDeclaration(): ActionSurfaceDeclaration
|
||||
return ActionSurfaceDeclaration::forResource(ActionSurfaceProfile::CrudListAndView)
|
||||
->satisfy(ActionSurfaceSlot::ListHeader, 'Header actions support filtered findings operations (legacy acknowledge-all-matching remains until bulk workflow migration).')
|
||||
->satisfy(ActionSurfaceSlot::InspectAffordance, ActionSurfaceInspectAffordance::ClickableRow->value)
|
||||
->satisfy(ActionSurfaceSlot::ListRowMoreMenu, 'Secondary row actions are grouped under "More".')
|
||||
->satisfy(ActionSurfaceSlot::ListRowMoreMenu, 'Secondary workflow actions are grouped under "More"; the only inline row action is the related-record drill-down.')
|
||||
->satisfy(ActionSurfaceSlot::ListBulkMoreGroup, 'Bulk actions are grouped under "More".')
|
||||
->exempt(ActionSurfaceSlot::ListEmptyState, 'Findings are generated by drift detection and intentionally have no create CTA.')
|
||||
->satisfy(ActionSurfaceSlot::DetailHeader, 'View page exposes capability-gated workflow actions for finding lifecycle management.');
|
||||
@ -1643,6 +1643,7 @@ public static function reopenAction(): Actions\Action
|
||||
*/
|
||||
private static function runWorkflowMutation(Finding $record, string $successTitle, callable $callback): void
|
||||
{
|
||||
$pageRecord = $record;
|
||||
$record = static::resolveProtectedFindingRecordOrFail($record);
|
||||
$tenant = static::resolveTenantContextForCurrentPanel();
|
||||
$user = auth()->user();
|
||||
@ -1671,6 +1672,8 @@ private static function runWorkflowMutation(Finding $record, string $successTitl
|
||||
|
||||
try {
|
||||
$callback($record, $tenant, $user);
|
||||
|
||||
$pageRecord->refresh();
|
||||
} catch (InvalidArgumentException $e) {
|
||||
Notification::make()
|
||||
->title('Workflow action failed')
|
||||
|
||||
@ -108,7 +108,9 @@ private function getMembership(User $user, Tenant $tenant): ?array
|
||||
/**
|
||||
* Prime membership cache for a set of tenants in one query.
|
||||
*
|
||||
* Used to avoid N+1 queries for bulk selection authorization.
|
||||
* Used to avoid N+1 queries for bulk selection authorization while still
|
||||
* reflecting membership changes that may have happened earlier in the same
|
||||
* request or test process.
|
||||
*
|
||||
* @param array<int, int|string> $tenantIds
|
||||
*/
|
||||
@ -120,26 +122,14 @@ public function primeMemberships(User $user, array $tenantIds): void
|
||||
return;
|
||||
}
|
||||
|
||||
$missingTenantIds = [];
|
||||
foreach ($tenantIds as $tenantId) {
|
||||
$cacheKey = "membership_{$user->id}_{$tenantId}";
|
||||
if (! array_key_exists($cacheKey, $this->resolvedMemberships)) {
|
||||
$missingTenantIds[] = $tenantId;
|
||||
}
|
||||
}
|
||||
|
||||
if ($missingTenantIds === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$memberships = TenantMembership::query()
|
||||
->where('user_id', $user->id)
|
||||
->whereIn('tenant_id', $missingTenantIds)
|
||||
->whereIn('tenant_id', $tenantIds)
|
||||
->get(['tenant_id', 'role', 'source', 'source_ref']);
|
||||
|
||||
$byTenantId = $memberships->keyBy('tenant_id');
|
||||
|
||||
foreach ($missingTenantIds as $tenantId) {
|
||||
foreach ($tenantIds as $tenantId) {
|
||||
$cacheKey = "membership_{$user->id}_{$tenantId}";
|
||||
$membership = $byTenantId->get($tenantId);
|
||||
$this->resolvedMemberships[$cacheKey] = $membership?->toArray();
|
||||
|
||||
@ -5,12 +5,15 @@
|
||||
namespace App\Services\TenantReviews;
|
||||
|
||||
use App\Models\EvidenceSnapshot;
|
||||
use App\Models\ReviewPack;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\TenantReview;
|
||||
use App\Models\User;
|
||||
use App\Services\Audit\WorkspaceAuditLogger;
|
||||
use App\Support\Audit\AuditActionId;
|
||||
use App\Support\TenantReviewStatus;
|
||||
use App\Support\Ui\DerivedState\DerivedStateFamily;
|
||||
use App\Support\Ui\DerivedState\RequestScopedDerivedStateStore;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use InvalidArgumentException;
|
||||
|
||||
@ -20,6 +23,7 @@ public function __construct(
|
||||
private readonly TenantReviewReadinessGate $readinessGate,
|
||||
private readonly TenantReviewService $reviewService,
|
||||
private readonly WorkspaceAuditLogger $auditLogger,
|
||||
private readonly RequestScopedDerivedStateStore $derivedStateStore,
|
||||
) {}
|
||||
|
||||
public function publish(TenantReview $review, User $user): TenantReview
|
||||
@ -64,6 +68,8 @@ public function publish(TenantReview $review, User $user): TenantReview
|
||||
tenant: $tenant,
|
||||
);
|
||||
|
||||
$this->invalidateArtifactTruthCache($review);
|
||||
|
||||
return $review->refresh()->load(['tenant', 'sections', 'currentExportReviewPack']);
|
||||
}
|
||||
|
||||
@ -104,6 +110,8 @@ public function archive(TenantReview $review, User $user): TenantReview
|
||||
tenant: $tenant,
|
||||
);
|
||||
|
||||
$this->invalidateArtifactTruthCache($review);
|
||||
|
||||
return $review->refresh()->load(['tenant', 'sections', 'currentExportReviewPack']);
|
||||
}
|
||||
|
||||
@ -126,7 +134,7 @@ public function createNextReview(TenantReview $review, User $user, ?EvidenceSnap
|
||||
throw new InvalidArgumentException('An eligible evidence snapshot is required to create the next review.');
|
||||
}
|
||||
|
||||
return DB::transaction(function () use ($review, $user, $snapshot, $tenant): TenantReview {
|
||||
$nextReview = DB::transaction(function () use ($review, $user, $snapshot, $tenant): TenantReview {
|
||||
$nextReview = $this->reviewService->create($tenant, $snapshot, $user);
|
||||
|
||||
if ((int) $nextReview->getKey() !== (int) $review->getKey()) {
|
||||
@ -156,5 +164,23 @@ public function createNextReview(TenantReview $review, User $user, ?EvidenceSnap
|
||||
|
||||
return $nextReview->refresh()->load(['tenant', 'evidenceSnapshot', 'sections', 'operationRun', 'initiator', 'publisher']);
|
||||
});
|
||||
|
||||
$this->invalidateArtifactTruthCache($review);
|
||||
$this->invalidateArtifactTruthCache($nextReview);
|
||||
|
||||
return $nextReview;
|
||||
}
|
||||
|
||||
private function invalidateArtifactTruthCache(TenantReview $review): void
|
||||
{
|
||||
$this->derivedStateStore->invalidateModel(DerivedStateFamily::ArtifactTruth, $review, 'tenant_review');
|
||||
|
||||
$review->loadMissing('currentExportReviewPack');
|
||||
|
||||
$pack = $review->currentExportReviewPack;
|
||||
|
||||
if ($pack instanceof ReviewPack) {
|
||||
$this->derivedStateStore->invalidateModel(DerivedStateFamily::ArtifactTruth, $pack, 'review_pack');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -58,7 +58,7 @@ public function resolve(ReferenceDescriptor $descriptor): ResolvedReference
|
||||
secondaryLabel: 'Backup set #'.$backupSet->getKey(),
|
||||
linkTarget: new ReferenceLinkTarget(
|
||||
targetKind: ReferenceClass::BackupSet->value,
|
||||
url: BackupSetResource::getUrl('view', ['record' => $backupSet], tenant: $backupSet->tenant),
|
||||
url: BackupSetResource::getUrl('view', ['record' => $backupSet], panel: 'tenant', tenant: $backupSet->tenant),
|
||||
actionLabel: 'View backup set',
|
||||
contextBadge: 'Tenant',
|
||||
),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user