TenantAtlas/apps/platform/app/Support/GovernanceInbox/ReviewPublicationResolutionInboxProvider.php
ahmido 9912d94563 feat: add governance inbox resolution intake (#460)
Automated PR created by Codex via Gitea API.

Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de>
Reviewed-on: #460
2026-06-20 07:46:12 +00:00

822 lines
32 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Support\GovernanceInbox;
use App\Filament\Resources\EnvironmentReviewResource;
use App\Models\EnvironmentReview;
use App\Models\EvidenceSnapshot;
use App\Models\ManagedEnvironment;
use App\Models\OperationRun;
use App\Models\ReviewPack;
use App\Models\ReviewPublicationResolutionCase;
use App\Models\ReviewPublicationResolutionStep;
use App\Models\User;
use App\Models\Workspace;
use App\Support\Navigation\CanonicalNavigationContext;
use App\Support\OperationCatalog;
use App\Support\OperationRunLinks;
use App\Support\OperationRunOutcome;
use App\Support\OperationRunStatus;
use App\Support\OperationRunType;
use App\Support\ReviewPublicationResolution\ResolutionProofCurrentness;
use App\Support\ReviewPublicationResolution\ResolutionProofEvaluation;
use App\Support\ReviewPublicationResolution\ResolutionProofUsability;
use App\Support\ReviewPublicationResolution\ResolutionProofVisibility;
use App\Support\ReviewPublicationResolution\ReviewPublicationResolutionCaseStatus;
use App\Support\ReviewPublicationResolution\ReviewPublicationResolutionStepAuthorizer;
use App\Support\ReviewPublicationResolution\ReviewPublicationResolutionStepKey;
use App\Support\ReviewPublicationResolution\ReviewPublicationResolutionStepStatus;
use Illuminate\Support\Facades\Gate;
final readonly class ReviewPublicationResolutionInboxProvider
{
public const string FAMILY_KEY = 'review_publication_resolution';
/**
* @var list<string>
*/
public const array STATUS_FILTERS = [
'needs_attention',
'needs_recheck',
'waiting',
'ready_to_continue',
'failed',
'blocked',
];
/**
* @var list<string>
*/
public const array UPDATED_FILTERS = [
'last_24_hours',
'last_7_days',
'last_30_days',
];
public function __construct(
private ReviewPublicationResolutionStepAuthorizer $stepAuthorizer,
) {}
/**
* @param array<int, ManagedEnvironment> $reviewTenants
* @return array<string, mixed>
*/
public function section(
User $user,
Workspace $workspace,
array $reviewTenants,
?ManagedEnvironment $selectedTenant,
?string $selectedStatus,
?string $selectedUpdated,
?CanonicalNavigationContext $navigationContext,
int $previewLimit,
): array {
$tenantIds = $this->scopedTenantIds($reviewTenants, $selectedTenant);
if ($tenantIds === []) {
return $this->emptySection($selectedTenant, $selectedStatus, $selectedUpdated);
}
$query = ReviewPublicationResolutionCase::query()
->forWorkspace((int) $workspace->getKey())
->active()
->whereIn('managed_environment_id', $tenantIds);
if (in_array($selectedUpdated, self::UPDATED_FILTERS, true)) {
$query->where('updated_at', '>=', $this->updatedSince($selectedUpdated));
}
$previewEntries = collect();
$count = 0;
$statusCounts = [];
$shouldFilterStatus = in_array($selectedStatus, self::STATUS_FILTERS, true);
(clone $query)
->with([
'tenant',
'environmentReview.tenant',
'steps.operationRun',
'assignee',
'creator',
])
->chunkById(100, function ($cases) use (
$user,
$navigationContext,
$selectedStatus,
$shouldFilterStatus,
$previewLimit,
&$previewEntries,
&$count,
&$statusCounts,
): void {
foreach ($cases as $case) {
if (! $case instanceof ReviewPublicationResolutionCase || ! Gate::forUser($user)->allows('view', $case)) {
continue;
}
$entry = $this->entry($case, $user, $navigationContext);
if (! is_array($entry)) {
continue;
}
if ($shouldFilterStatus && ($entry['inbox_status'] ?? null) !== $selectedStatus) {
continue;
}
$count++;
$status = (string) ($entry['inbox_status'] ?? 'needs_recheck');
$statusCounts[$status] = (int) ($statusCounts[$status] ?? 0) + 1;
$previewEntries->push($entry);
$previewEntries = $this->sortEntries($previewEntries)
->take($previewLimit)
->values();
}
});
return [
'key' => self::FAMILY_KEY,
'label' => 'Review publication work',
'count' => $count,
'summary' => $this->summary($count, $statusCounts, $selectedStatus, $selectedUpdated),
'dominant_action_label' => 'Review publication work',
'dominant_action_url' => null,
'entries' => $previewEntries
->map(fn (array $entry): array => $this->withoutInternalSortKeys($entry))
->values()
->all(),
'empty_state' => $this->emptyState($selectedTenant, $selectedStatus, $selectedUpdated),
];
}
public static function statusLabel(string $status): string
{
return match ($status) {
'needs_attention' => 'Needs attention',
'needs_recheck' => 'Needs re-check',
'waiting' => 'Waiting',
'ready_to_continue' => 'Ready to continue',
'failed' => 'Failed',
'blocked' => 'Blocked',
default => 'Needs attention',
};
}
public static function updatedLabel(?string $updated): string
{
return match ($updated) {
'last_24_hours' => 'Last 24 hours',
'last_7_days' => 'Last 7 days',
'last_30_days' => 'Last 30 days',
default => 'Any time',
};
}
/**
* @param array<int, ManagedEnvironment> $reviewTenants
* @return list<int>
*/
private function scopedTenantIds(array $reviewTenants, ?ManagedEnvironment $selectedTenant): array
{
if ($selectedTenant instanceof ManagedEnvironment) {
return array_key_exists((int) $selectedTenant->getKey(), $reviewTenants)
? [(int) $selectedTenant->getKey()]
: [];
}
return array_map(
static fn (ManagedEnvironment $tenant): int => (int) $tenant->getKey(),
$reviewTenants,
);
}
/**
* @return array<string, mixed>|null
*/
private function entry(
ReviewPublicationResolutionCase $case,
User $user,
?CanonicalNavigationContext $navigationContext,
): ?array {
$tenant = $case->tenant;
$review = $case->environmentReview;
if (! $tenant instanceof ManagedEnvironment || ! $review instanceof EnvironmentReview) {
return null;
}
$currentStep = $case->currentStep();
$canExecute = $this->stepAuthorizer->canExecuteCurrentStep($user, $case);
$operationAction = $this->operationAction($case, $currentStep, $user, $tenant, $navigationContext);
$inboxStatus = $this->inboxStatus($case, $currentStep, $canExecute, $operationAction !== null);
$primaryAction = $this->primaryAction($inboxStatus, $operationAction);
$resolutionUrl = EnvironmentReviewResource::environmentScopedUrl(
'resolve-publication',
['record' => $review],
$tenant,
);
$reviewUrl = EnvironmentReviewResource::environmentScopedUrl(
'view',
['record' => $review],
$tenant,
);
return [
'family_key' => self::FAMILY_KEY,
'source_model' => ReviewPublicationResolutionCase::class,
'source_key' => (string) $case->getKey(),
'managed_environment_id' => (int) $tenant->getKey(),
'tenant_label' => $tenant->name,
'headline' => $this->headline($inboxStatus),
'subline' => $this->subline($case, $review, $currentStep),
'urgency_rank' => $this->urgencyRank($inboxStatus),
'status_label' => self::statusLabel($inboxStatus),
'inbox_status' => $inboxStatus,
'destination_url' => $resolutionUrl,
'reason_label' => $this->reasonLabel($inboxStatus, $currentStep, $canExecute),
'impact_label' => $this->impactLabel($inboxStatus),
'owner_label' => $case->assignee?->name ?? $case->creator?->name ?? 'Owner unavailable',
'due_label' => 'No due date set',
'evidence_label' => $this->evidenceLabel($currentStep),
'exception_label' => 'Publication preparation',
'primary_action_label' => $primaryAction['label'],
'primary_action_url' => $primaryAction['url'] ?? $resolutionUrl,
'secondary_actions' => array_values(array_filter([
[
'label' => 'Open review',
'url' => $reviewUrl,
],
($operationAction['url'] ?? null) !== ($primaryAction['url'] ?? null) ? $operationAction : null,
])),
'linked_records' => array_values(array_filter([
[
'label' => 'Resolution preparation',
'url' => $resolutionUrl,
],
[
'label' => 'Review',
'url' => $reviewUrl,
],
$operationAction,
])),
'updated_sort' => $case->updated_at?->getTimestamp() ?? 0,
'back_label' => 'Back to governance inbox',
];
}
/**
* @return array{label: string, url: string}|null
*/
private function operationAction(
ReviewPublicationResolutionCase $case,
?ReviewPublicationResolutionStep $currentStep,
User $user,
ManagedEnvironment $tenant,
?CanonicalNavigationContext $navigationContext,
): ?array {
if (! $this->canDiscloseOperationRun($case, $currentStep, $user)) {
return null;
}
/** @var OperationRun $operationRun */
$operationRun = $currentStep->operationRun;
return [
'label' => OperationRunLinks::openLabel(),
'url' => OperationRunLinks::view($operationRun, $tenant, $navigationContext),
];
}
private function canDiscloseOperationRun(
ReviewPublicationResolutionCase $case,
?ReviewPublicationResolutionStep $currentStep,
User $user,
): bool {
if (! $currentStep instanceof ReviewPublicationResolutionStep) {
return false;
}
$operationRun = $currentStep->operationRun;
$stepKey = $currentStep->stepKeyEnum();
if (! $operationRun instanceof OperationRun || ! $stepKey instanceof ReviewPublicationResolutionStepKey) {
return false;
}
if (! is_numeric($currentStep->operation_run_id) || (int) $currentStep->operation_run_id !== (int) $operationRun->getKey()) {
return false;
}
if ((int) $operationRun->workspace_id !== (int) $case->workspace_id
|| (int) $operationRun->managed_environment_id !== (int) $case->managed_environment_id) {
return false;
}
if (! Gate::forUser($user)->allows('view', $operationRun)) {
return false;
}
if (! $this->operationTypeMatchesStep($operationRun, $stepKey)) {
return false;
}
if (! $this->operationStateMatchesStep($operationRun, $currentStep->statusEnum())) {
return false;
}
if (! $this->operationContextMatchesCase($operationRun, $case)) {
return false;
}
if (! $this->safeCurrentProofMetadata($currentStep)) {
return false;
}
return $this->proofMatchesReview($case, $currentStep, $operationRun);
}
private function operationTypeMatchesStep(OperationRun $operationRun, ReviewPublicationResolutionStepKey $stepKey): bool
{
$actualType = OperationCatalog::canonicalCode((string) $operationRun->type);
$expectedTypes = array_map(
static fn (string $type): string => OperationCatalog::canonicalCode($type),
$this->expectedOperationTypes($stepKey),
);
return in_array($actualType, $expectedTypes, true);
}
private function operationStateMatchesStep(OperationRun $operationRun, ReviewPublicationResolutionStepStatus $stepStatus): bool
{
if ($stepStatus === ReviewPublicationResolutionStepStatus::Running) {
return in_array((string) $operationRun->status, [
OperationRunStatus::Queued->value,
OperationRunStatus::Running->value,
], true)
&& (string) $operationRun->outcome === OperationRunOutcome::Pending->value;
}
if ($stepStatus === ReviewPublicationResolutionStepStatus::Failed) {
return (string) $operationRun->status === OperationRunStatus::Completed->value
&& in_array((string) $operationRun->outcome, [
OperationRunOutcome::Blocked->value,
OperationRunOutcome::Failed->value,
], true);
}
return false;
}
private function operationContextMatchesCase(OperationRun $operationRun, ReviewPublicationResolutionCase $case): bool
{
$context = is_array($operationRun->context) ? $operationRun->context : [];
foreach ([
'workspace_id' => (int) $case->workspace_id,
'managed_environment_id' => (int) $case->managed_environment_id,
'review_publication_resolution_case_id' => (int) $case->getKey(),
] as $key => $expectedValue) {
$value = $context[$key] ?? null;
if (! is_numeric($value) || (int) $value !== $expectedValue) {
return false;
}
}
$reviewIds = collect([
$context['environment_review_id'] ?? null,
$context['review_id'] ?? null,
])
->filter(fn (mixed $value): bool => is_numeric($value))
->map(fn (mixed $value): int => (int) $value)
->unique()
->values();
if ($reviewIds->count() !== 1 || $reviewIds->first() !== (int) $case->environment_review_id) {
return false;
}
return ($context['trigger'] ?? null) === 'review_publication_resolution';
}
private function safeCurrentProofMetadata(ReviewPublicationResolutionStep $step): bool
{
if ((string) data_get($step->metadata, 'proof_currentness') !== ResolutionProofCurrentness::Current->value) {
return false;
}
if ((string) data_get($step->metadata, 'proof_visibility') !== ResolutionProofVisibility::OperatorVisible->value) {
return false;
}
if (! in_array((string) data_get($step->metadata, 'proof_usability'), [
ResolutionProofUsability::Usable->value,
ResolutionProofUsability::UsableWithWarning->value,
ResolutionProofUsability::InspectionOnly->value,
], true)) {
return false;
}
$summary = data_get($step->metadata, 'proof_summary');
if (! is_array($summary)) {
return false;
}
return ResolutionProofEvaluation::sanitizeSummary($summary) === $summary;
}
private function proofMatchesReview(
ReviewPublicationResolutionCase $case,
ReviewPublicationResolutionStep $step,
OperationRun $operationRun,
): bool {
$stepKey = $step->stepKeyEnum();
if (! $stepKey instanceof ReviewPublicationResolutionStepKey || ! is_string($step->proof_type) || ! is_numeric($step->proof_id)) {
return false;
}
if ($stepKey === ReviewPublicationResolutionStepKey::CompleteRequiredReports) {
return $step->proof_type === 'operation_run'
&& (int) $step->proof_id === (int) $operationRun->getKey();
}
return match ($stepKey) {
ReviewPublicationResolutionStepKey::CollectEvidenceSnapshot => $step->proof_type === 'evidence_snapshot'
&& EvidenceSnapshot::query()
->whereKey((int) $step->proof_id)
->where('workspace_id', (int) $case->workspace_id)
->where('managed_environment_id', (int) $case->managed_environment_id)
->where('operation_run_id', (int) $operationRun->getKey())
->exists(),
ReviewPublicationResolutionStepKey::RefreshReviewComposition => $step->proof_type === 'environment_review'
&& (int) $step->proof_id === (int) $case->environment_review_id
&& EnvironmentReview::query()
->whereKey((int) $case->environment_review_id)
->where('workspace_id', (int) $case->workspace_id)
->where('managed_environment_id', (int) $case->managed_environment_id)
->where('operation_run_id', (int) $operationRun->getKey())
->exists(),
ReviewPublicationResolutionStepKey::GenerateReviewPack => $step->proof_type === 'review_pack'
&& ReviewPack::query()
->whereKey((int) $step->proof_id)
->where('workspace_id', (int) $case->workspace_id)
->where('managed_environment_id', (int) $case->managed_environment_id)
->where('environment_review_id', (int) $case->environment_review_id)
->where('operation_run_id', (int) $operationRun->getKey())
->exists(),
default => false,
};
}
/**
* @return list<string>
*/
private function expectedOperationTypes(ReviewPublicationResolutionStepKey $stepKey): array
{
return match ($stepKey) {
ReviewPublicationResolutionStepKey::CompleteRequiredReports => [
'provider.connection.check',
OperationRunType::EntraAdminRolesScan->value,
],
ReviewPublicationResolutionStepKey::CollectEvidenceSnapshot => [
OperationRunType::EvidenceSnapshotGenerate->value,
],
ReviewPublicationResolutionStepKey::RefreshReviewComposition => [
OperationRunType::EnvironmentReviewCompose->value,
],
ReviewPublicationResolutionStepKey::GenerateReviewPack => [
OperationRunType::ReviewPackGenerate->value,
],
ReviewPublicationResolutionStepKey::ValidateReviewReadiness,
ReviewPublicationResolutionStepKey::ReturnToPublication => [],
};
}
/**
* @return array<string, mixed>
*/
private function emptySection(
?ManagedEnvironment $selectedTenant,
?string $selectedStatus,
?string $selectedUpdated,
): array {
return [
'key' => self::FAMILY_KEY,
'label' => 'Review publication work',
'count' => 0,
'summary' => $this->summary(0, [], $selectedStatus, $selectedUpdated),
'dominant_action_label' => 'Review publication work',
'dominant_action_url' => null,
'entries' => [],
'empty_state' => $this->emptyState($selectedTenant, $selectedStatus, $selectedUpdated),
];
}
/**
* @param array<string, int> $statusCounts
*/
private function summary(int $count, array $statusCounts, ?string $selectedStatus, ?string $selectedUpdated): string
{
if ($count === 0) {
return $this->filterSummaryPrefix($selectedStatus, $selectedUpdated).'No active review publication preparation is visible.';
}
$attentionCount = (int) ($statusCounts['needs_attention'] ?? 0);
$waitingCount = (int) ($statusCounts['waiting'] ?? 0);
$blockedCount = (int) ($statusCounts['failed'] ?? 0) + (int) ($statusCounts['blocked'] ?? 0);
return sprintf(
'%s%d active review publication preparation %s visible; %d need attention, %d waiting, %d failed or blocked.',
$this->filterSummaryPrefix($selectedStatus, $selectedUpdated),
$count,
$count === 1 ? 'item is' : 'items are',
$attentionCount,
$waitingCount,
$blockedCount,
);
}
private function filterSummaryPrefix(?string $selectedStatus, ?string $selectedUpdated): string
{
$parts = [];
if (in_array($selectedStatus, self::STATUS_FILTERS, true)) {
$parts[] = self::statusLabel($selectedStatus);
}
if (in_array($selectedUpdated, self::UPDATED_FILTERS, true)) {
$parts[] = self::updatedLabel($selectedUpdated);
}
return $parts === [] ? '' : implode(' / ', $parts).': ';
}
private function emptyState(?ManagedEnvironment $selectedTenant, ?string $selectedStatus, ?string $selectedUpdated): string
{
if (in_array($selectedStatus, self::STATUS_FILTERS, true) || in_array($selectedUpdated, self::UPDATED_FILTERS, true)) {
return 'No review publication preparation work matches these filters right now.';
}
if ($selectedTenant instanceof ManagedEnvironment) {
return 'No review publication preparation work matches this environment filter right now.';
}
return 'No active review publication preparation work needs attention right now.';
}
private function headline(string $status): string
{
return match ($status) {
'waiting' => 'Review preparation is running',
'ready_to_continue' => 'Review preparation can continue',
'failed' => 'Review preparation action failed',
'blocked' => 'Review preparation needs operator access',
'needs_recheck' => 'Review preparation needs re-check',
default => 'Review cannot be published yet',
};
}
private function subline(
ReviewPublicationResolutionCase $case,
EnvironmentReview $review,
?ReviewPublicationResolutionStep $currentStep,
): string {
$stepLabel = $this->stepLabel($currentStep?->stepKeyEnum());
$generatedAt = $review->generated_at?->format('M j, Y H:i') ?? 'date unavailable';
return sprintf(
'%s · Review generated %s · Case updated %s',
$stepLabel,
$generatedAt,
$case->updated_at?->diffForHumans() ?? 'recently',
);
}
private function inboxStatus(
ReviewPublicationResolutionCase $case,
?ReviewPublicationResolutionStep $currentStep,
bool $canExecute,
bool $hasValidatedOperation,
): string {
$caseStatus = $case->statusEnum();
$stepStatus = $currentStep?->statusEnum();
$stepKey = $currentStep?->stepKeyEnum();
if (! $currentStep instanceof ReviewPublicationResolutionStep || ! $stepStatus instanceof ReviewPublicationResolutionStepStatus) {
return 'needs_recheck';
}
if ($stepStatus === ReviewPublicationResolutionStepStatus::Running) {
return $hasValidatedOperation ? 'waiting' : 'needs_recheck';
}
if ($stepStatus === ReviewPublicationResolutionStepStatus::Failed) {
return $hasValidatedOperation ? 'failed' : 'needs_recheck';
}
if ($caseStatus === ReviewPublicationResolutionCaseStatus::ReadyToContinue
|| $stepKey === ReviewPublicationResolutionStepKey::ReturnToPublication) {
if ($stepKey !== ReviewPublicationResolutionStepKey::ReturnToPublication
|| ! $this->safeReadyToContinueProofMetadata($case, $currentStep)) {
return 'needs_recheck';
}
return $canExecute ? 'ready_to_continue' : 'blocked';
}
if ($stepStatus === ReviewPublicationResolutionStepStatus::Actionable
|| $stepStatus === ReviewPublicationResolutionStepStatus::Pending) {
return $canExecute ? 'needs_attention' : 'blocked';
}
if ($caseStatus === ReviewPublicationResolutionCaseStatus::Blocked) {
return 'blocked';
}
return 'needs_recheck';
}
private function reasonLabel(string $status, ?ReviewPublicationResolutionStep $currentStep, bool $canExecute): string
{
if ($status === 'failed') {
return 'The linked preparation operation failed and needs inspection before retry.';
}
if ($status === 'waiting') {
return 'TenantPilot is waiting for the linked preparation operation to finish.';
}
if ($status === 'ready_to_continue') {
return 'All preparation checks are resolved and the review can continue from the existing workflow.';
}
if ($status === 'blocked') {
return $canExecute
? 'The preparation case is blocked and needs inspection.'
: 'You can inspect this preparation flow, but you cannot run the next action.';
}
if ($status === 'needs_recheck') {
return 'Current proof is missing, stale, or not safe enough to determine the next action from the inbox.';
}
return match ($currentStep?->stepKeyEnum()) {
ReviewPublicationResolutionStepKey::CompleteRequiredReports => 'Required reports are missing.',
ReviewPublicationResolutionStepKey::CollectEvidenceSnapshot => 'A current evidence snapshot is required.',
ReviewPublicationResolutionStepKey::RefreshReviewComposition => 'The review must be refreshed from current evidence.',
ReviewPublicationResolutionStepKey::GenerateReviewPack => 'The customer-ready export must be prepared.',
ReviewPublicationResolutionStepKey::ReturnToPublication => 'The review is ready to return to publication.',
default => 'Publication preparation needs an operator decision.',
};
}
private function impactLabel(string $status): string
{
return match ($status) {
'waiting' => 'No duplicate start action is exposed while preparation is already running.',
'ready_to_continue' => 'Publishing stays on the review page after the preparation flow returns there.',
'failed' => 'Publication remains blocked until the failed preparation operation is inspected and retried.',
'blocked' => 'Publication remains blocked until an authorized operator continues the preparation flow.',
'needs_recheck' => 'The source preparation page must refresh state before the inbox can classify the next action.',
default => 'Publication remains blocked until this preparation step is completed.',
};
}
private function evidenceLabel(?ReviewPublicationResolutionStep $currentStep): string
{
if (! $currentStep instanceof ReviewPublicationResolutionStep) {
return 'Proof needs re-check';
}
return match ($currentStep->stepKeyEnum()) {
ReviewPublicationResolutionStepKey::CompleteRequiredReports => 'Required reports',
ReviewPublicationResolutionStepKey::CollectEvidenceSnapshot => 'Evidence snapshot',
ReviewPublicationResolutionStepKey::RefreshReviewComposition => 'Review composition',
ReviewPublicationResolutionStepKey::GenerateReviewPack => 'Review export',
ReviewPublicationResolutionStepKey::ReturnToPublication => 'Publication readiness',
default => 'Readiness proof',
};
}
private function stepLabel(?ReviewPublicationResolutionStepKey $stepKey): string
{
return match ($stepKey) {
ReviewPublicationResolutionStepKey::ValidateReviewReadiness => 'Check readiness',
ReviewPublicationResolutionStepKey::CompleteRequiredReports => 'Update required reports',
ReviewPublicationResolutionStepKey::CollectEvidenceSnapshot => 'Collect evidence',
ReviewPublicationResolutionStepKey::RefreshReviewComposition => 'Refresh review',
ReviewPublicationResolutionStepKey::GenerateReviewPack => 'Prepare export',
ReviewPublicationResolutionStepKey::ReturnToPublication => 'Return to review',
default => 'Preparation state',
};
}
private function urgencyRank(string $status): int
{
return match ($status) {
'failed' => 0,
'blocked' => 1,
'needs_attention' => 2,
'needs_recheck' => 3,
'ready_to_continue' => 4,
'waiting' => 5,
default => 99,
};
}
/**
* @param \Illuminate\Support\Collection<int, array<string, mixed>> $entries
* @return \Illuminate\Support\Collection<int, array<string, mixed>>
*/
private function sortEntries(\Illuminate\Support\Collection $entries): \Illuminate\Support\Collection
{
return $entries
->sortBy([
fn (array $first, array $second): int => (int) ($first['urgency_rank'] ?? 999) <=> (int) ($second['urgency_rank'] ?? 999),
fn (array $first, array $second): int => (int) ($second['updated_sort'] ?? 0) <=> (int) ($first['updated_sort'] ?? 0),
])
->values();
}
/**
* @return array{label: string, url: string|null}
*/
private function primaryAction(string $inboxStatus, ?array $operationAction): array
{
if ($inboxStatus === 'waiting' && is_array($operationAction) && is_string($operationAction['url'] ?? null)) {
return [
'label' => 'Open operation',
'url' => $operationAction['url'],
];
}
return [
'label' => in_array($inboxStatus, ['needs_attention', 'ready_to_continue'], true)
? 'Continue preparation'
: 'Inspect preparation',
'url' => null,
];
}
private function safeReadyToContinueProofMetadata(
ReviewPublicationResolutionCase $case,
ReviewPublicationResolutionStep $step,
): bool {
if ($step->proof_type !== 'environment_review'
|| ! is_numeric($step->proof_id)
|| (int) $step->proof_id !== (int) $case->environment_review_id) {
return false;
}
if ((string) data_get($step->metadata, 'proof_currentness') !== ResolutionProofCurrentness::Current->value) {
return false;
}
if ((string) data_get($step->metadata, 'proof_visibility') !== ResolutionProofVisibility::OperatorVisible->value) {
return false;
}
if (! in_array((string) data_get($step->metadata, 'proof_usability'), [
ResolutionProofUsability::Usable->value,
ResolutionProofUsability::UsableWithWarning->value,
], true)) {
return false;
}
$summary = data_get($step->metadata, 'proof_summary');
if (! is_array($summary)) {
return false;
}
return ResolutionProofEvaluation::sanitizeSummary($summary) === $summary;
}
private function updatedSince(string $selectedUpdated): \Illuminate\Support\Carbon
{
return match ($selectedUpdated) {
'last_24_hours' => now()->subDay(),
'last_7_days' => now()->subDays(7),
'last_30_days' => now()->subDays(30),
default => now()->subYears(50),
};
}
/**
* @param array<string, mixed> $entry
* @return array<string, mixed>
*/
private function withoutInternalSortKeys(array $entry): array
{
unset($entry['updated_sort']);
return $entry;
}
}