TenantAtlas/apps/platform/app/Services/PortfolioTriage/ManagedEnvironmentTriageReviewService.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

166 lines
5.9 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Services\PortfolioTriage;
use App\Models\ManagedEnvironment;
use App\Models\ManagedEnvironmentTriageReview;
use App\Models\User;
use App\Services\Audit\WorkspaceAuditLogger;
use App\Support\Audit\AuditActionId;
use App\Support\BackupHealth\TenantBackupHealthAssessment;
use App\Support\PortfolioTriage\ManagedEnvironmentTriageReviewFingerprint;
use Illuminate\Support\Facades\DB;
use InvalidArgumentException;
final readonly class ManagedEnvironmentTriageReviewService
{
public function __construct(
private ManagedEnvironmentTriageReviewFingerprint $fingerprints,
private WorkspaceAuditLogger $auditLogger,
) {}
/**
* @param array<string, mixed>|null $recoveryEvidence
*/
public function markReviewed(
ManagedEnvironment $tenant,
string $concernFamily,
?TenantBackupHealthAssessment $backupHealth = null,
?array $recoveryEvidence = null,
?User $actor = null,
): ManagedEnvironmentTriageReview {
return $this->store(
tenant: $tenant,
concernFamily: $concernFamily,
manualState: ManagedEnvironmentTriageReview::STATE_REVIEWED,
backupHealth: $backupHealth,
recoveryEvidence: $recoveryEvidence,
actor: $actor,
);
}
/**
* @param array<string, mixed>|null $recoveryEvidence
*/
public function markFollowUpNeeded(
ManagedEnvironment $tenant,
string $concernFamily,
?TenantBackupHealthAssessment $backupHealth = null,
?array $recoveryEvidence = null,
?User $actor = null,
): ManagedEnvironmentTriageReview {
return $this->store(
tenant: $tenant,
concernFamily: $concernFamily,
manualState: ManagedEnvironmentTriageReview::STATE_FOLLOW_UP_NEEDED,
backupHealth: $backupHealth,
recoveryEvidence: $recoveryEvidence,
actor: $actor,
);
}
/**
* @param array<string, mixed>|null $recoveryEvidence
*/
private function store(
ManagedEnvironment $tenant,
string $concernFamily,
string $manualState,
?TenantBackupHealthAssessment $backupHealth,
?array $recoveryEvidence,
?User $actor,
): ManagedEnvironmentTriageReview {
if (! in_array($manualState, ManagedEnvironmentTriageReview::MANUAL_STATES, true)) {
throw new InvalidArgumentException('Unsupported triage review state.');
}
if (! is_numeric($tenant->workspace_id) || (int) $tenant->workspace_id <= 0) {
throw new InvalidArgumentException('ManagedEnvironment must belong to a workspace.');
}
$currentConcern = $this->fingerprints->forConcernFamily($concernFamily, $backupHealth, $recoveryEvidence);
if ($currentConcern === null) {
throw new InvalidArgumentException('No current triage concern is available for review.');
}
$workspaceId = (int) $tenant->workspace_id;
$now = now();
/** @var ManagedEnvironmentTriageReview $review */
$review = DB::transaction(function () use (
$tenant,
$workspaceId,
$manualState,
$currentConcern,
$actor,
$now,
): ManagedEnvironmentTriageReview {
ManagedEnvironmentTriageReview::query()
->forWorkspace($workspaceId)
->forTenant((int) $tenant->getKey())
->where('concern_family', $currentConcern['concern_family'])
->active()
->update([
'resolved_at' => $now,
'updated_at' => $now,
]);
return ManagedEnvironmentTriageReview::query()->create([
'workspace_id' => $workspaceId,
'managed_environment_id' => (int) $tenant->getKey(),
'concern_family' => $currentConcern['concern_family'],
'current_state' => $manualState,
'reviewed_at' => $now,
'reviewed_by_user_id' => $actor?->getKey(),
'review_fingerprint' => $currentConcern['fingerprint'],
'review_snapshot' => $currentConcern['snapshot'],
'last_seen_matching_at' => $now,
'resolved_at' => null,
]);
});
$review->loadMissing('reviewer');
$this->auditLogger->log(
workspace: $tenant->workspace,
action: $manualState === ManagedEnvironmentTriageReview::STATE_REVIEWED
? AuditActionId::ManagedEnvironmentTriageReviewMarkedReviewed
: AuditActionId::ManagedEnvironmentTriageReviewMarkedFollowUpNeeded,
context: [
'metadata' => [
'concern_family' => $currentConcern['concern_family'],
'concern_state' => $currentConcern['concern_state'],
'reason_code' => $currentConcern['snapshot']['reasonCode'] ?? null,
'review_state' => $manualState,
],
],
actor: $actor,
resourceType: 'managed_environment_triage_review',
resourceId: (string) $review->getKey(),
targetLabel: $tenant->name,
tenant: $tenant,
summary: $this->summaryFor($currentConcern['concern_family'], $manualState),
);
return $review;
}
private function summaryFor(string $concernFamily, string $manualState): string
{
$family = match ($concernFamily) {
'backup_health' => 'Backup health',
'recovery_evidence' => 'Recovery evidence',
default => 'Portfolio concern',
};
$state = $manualState === ManagedEnvironmentTriageReview::STATE_REVIEWED
? 'reviewed'
: 'follow-up needed';
return sprintf('%s marked %s', $family, $state);
}
}