TenantAtlas/apps/platform/app/Support/ResolutionGuidance/Adapters/ProviderReadinessResolutionAdapter.php
Ahmed Darrazi 0329cb5420
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 1m0s
feat: implement operation run actionability system
2026-06-08 15:19:55 +02:00

766 lines
32 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Support\ResolutionGuidance\Adapters;
use App\Models\ManagedEnvironment;
use App\Models\OperationRun;
use App\Models\ProviderConnection;
use App\Services\Intune\ManagedEnvironmentRequiredPermissionsViewModelBuilder;
use App\Services\Providers\ProviderConnectionResolution;
use App\Services\Providers\ProviderConnectionResolver;
use App\Support\Links\RequiredPermissionsLinks;
use App\Support\ManagedEnvironmentLinks;
use App\Support\Navigation\CanonicalNavigationContext;
use App\Support\OperationRunLinks;
use App\Support\Providers\ProviderConsentStatus;
use App\Support\Providers\ProviderReasonCodes;
use App\Support\Providers\ProviderVerificationStatus;
use Illuminate\Support\Carbon;
use Illuminate\Support\Str;
final class ProviderReadinessResolutionAdapter
{
public const string SURFACE_PROVIDER_CONNECTIONS_INDEX = 'provider_connections.index';
public const string SURFACE_PROVIDER_CONNECTIONS_VIEW = 'provider_connections.view';
public const string SURFACE_PROVIDER_CONNECTIONS_EDIT = 'provider_connections.edit';
public const string SURFACE_REQUIRED_PERMISSIONS = 'required_permissions.page';
public function __construct(
private readonly ManagedEnvironmentRequiredPermissionsViewModelBuilder $requiredPermissions,
private readonly ProviderConnectionResolver $connections,
) {}
/**
* @return array<string, mixed>
*/
public function forEnvironment(
ManagedEnvironment $environment,
string $surface = self::SURFACE_PROVIDER_CONNECTIONS_INDEX,
): array {
$resolution = $this->connections->resolveDefault($environment, 'microsoft');
$connection = $resolution->connection;
return $this->buildCase(
environment: $environment,
connection: $connection,
resolution: $resolution,
permissions: $this->permissionSignals($environment),
latestVerificationRun: $connection instanceof ProviderConnection
? $this->latestVerificationRun($environment, $connection)
: null,
surface: $surface,
);
}
/**
* @return array<string, mixed>
*/
public function forConnection(
ManagedEnvironment $environment,
ProviderConnection $connection,
string $surface = self::SURFACE_PROVIDER_CONNECTIONS_VIEW,
): array {
$provider = trim((string) $connection->provider) !== ''
? trim((string) $connection->provider)
: 'microsoft';
return $this->buildCase(
environment: $environment,
connection: $connection,
resolution: $this->connections->validateConnection($environment, $provider, $connection),
permissions: $this->permissionSignals($environment),
latestVerificationRun: $this->latestVerificationRun($environment, $connection),
surface: $surface,
);
}
/**
* @param array{
* counts: array{missing_application:int,missing_delegated:int,present:int,error:int},
* freshness: array{last_refreshed_at:?string,is_stale:bool}
* } $permissions
* @return array<string, mixed>
*/
private function buildCase(
ManagedEnvironment $environment,
?ProviderConnection $connection,
ProviderConnectionResolution $resolution,
array $permissions,
?OperationRun $latestVerificationRun,
string $surface,
): array {
$counts = $permissions['counts'];
$freshness = $permissions['freshness'];
$missingApplication = (int) ($counts['missing_application'] ?? 0);
$missingDelegated = (int) ($counts['missing_delegated'] ?? 0);
$errorCount = (int) ($counts['error'] ?? 0);
if ($resolution->effectiveReasonCode() === ProviderReasonCodes::ProviderConnectionMissing) {
return $this->case(
key: 'provider_readiness.connection_missing',
severity: 'danger',
status: __('localization.provider_guidance.status_blocked'),
title: __('localization.provider_guidance.connection_missing_title'),
reason: __('localization.provider_guidance.connection_missing_reason'),
impact: __('localization.provider_guidance.connection_missing_impact'),
primaryAction: $this->providerConnectionsAction($environment),
secondaryActions: [
$this->environmentDashboardAction($environment),
],
technicalDetails: [
$this->technicalDetail(
__('localization.provider_guidance.detail_provider_label'),
__('localization.provider_guidance.detail_provider_value'),
),
$this->technicalDetail(
__('localization.provider_guidance.detail_verification_state_label'),
__('localization.provider_guidance.verification_not_run_detail'),
),
],
);
}
if ($this->isConsentBlocker($resolution->effectiveReasonCode())) {
return $this->case(
key: 'provider_readiness.admin_consent_required',
severity: 'danger',
status: __('localization.provider_guidance.status_blocked'),
title: __('localization.provider_guidance.admin_consent_required_title'),
reason: $this->consentReason($resolution->effectiveReasonCode()),
impact: __('localization.provider_guidance.admin_consent_required_impact'),
primaryAction: $this->adminConsentAction($environment),
secondaryActions: $this->secondaryActionsFor(
environment: $environment,
connection: $connection,
latestVerificationRun: $latestVerificationRun,
surface: $surface,
includeRequiredPermissions: true,
),
technicalDetails: $this->technicalDetails(
connection: $connection,
permissions: $permissions,
latestVerificationRun: $latestVerificationRun,
),
);
}
if (! $resolution->resolved) {
return $this->case(
key: 'provider_readiness.connection_review_required',
severity: 'danger',
status: __('localization.provider_guidance.status_blocked'),
title: $connection instanceof ProviderConnection && ! (bool) $connection->is_enabled
? __('localization.provider_guidance.connection_disabled_title')
: __('localization.provider_guidance.connection_review_title'),
reason: $connection instanceof ProviderConnection && ! (bool) $connection->is_enabled
? __('localization.provider_guidance.connection_disabled_reason')
: (trim((string) ($resolution->message ?? '')) !== ''
? trim((string) $resolution->message)
: __('localization.provider_guidance.connection_review_reason')),
impact: $connection instanceof ProviderConnection && ! (bool) $connection->is_enabled
? __('localization.provider_guidance.connection_disabled_impact')
: __('localization.provider_guidance.connection_review_impact'),
primaryAction: $this->providerConnectionReviewAction($environment, $connection, $surface),
secondaryActions: $this->secondaryActionsFor(
environment: $environment,
connection: $connection,
latestVerificationRun: $latestVerificationRun,
surface: $surface,
includeRequiredPermissions: true,
),
technicalDetails: $this->technicalDetails(
connection: $connection,
permissions: $permissions,
latestVerificationRun: $latestVerificationRun,
),
);
}
if ($missingApplication > 0) {
return $this->case(
key: 'provider_readiness.required_permissions_missing',
severity: 'danger',
status: __('localization.provider_guidance.status_blocked'),
title: $surface === self::SURFACE_REQUIRED_PERMISSIONS
? __('localization.provider_guidance.required_permissions_missing_title')
: __('localization.provider_guidance.provider_readiness_blocked_title'),
reason: __('localization.provider_guidance.required_application_permissions_reason'),
impact: __('localization.provider_guidance.required_application_permissions_impact'),
primaryAction: $surface === self::SURFACE_REQUIRED_PERMISSIONS
? $this->adminConsentAction($environment)
: $this->requiredPermissionsAction($environment),
secondaryActions: $this->secondaryActionsFor(
environment: $environment,
connection: $connection,
latestVerificationRun: $latestVerificationRun,
surface: $surface,
includeAdminConsent: $surface !== self::SURFACE_REQUIRED_PERMISSIONS,
),
technicalDetails: $this->technicalDetails(
connection: $connection,
permissions: $permissions,
latestVerificationRun: $latestVerificationRun,
),
);
}
if ($missingDelegated > 0) {
return $this->case(
key: 'provider_readiness.delegated_permissions_missing',
severity: 'warning',
status: __('localization.provider_guidance.status_action_required'),
title: $surface === self::SURFACE_REQUIRED_PERMISSIONS
? __('localization.provider_guidance.required_permissions_missing_title')
: __('localization.provider_guidance.provider_readiness_attention_title'),
reason: __('localization.provider_guidance.required_delegated_permissions_reason'),
impact: __('localization.provider_guidance.required_delegated_permissions_impact'),
primaryAction: $surface === self::SURFACE_REQUIRED_PERMISSIONS
? $this->adminConsentAction($environment)
: $this->requiredPermissionsAction($environment),
secondaryActions: $this->secondaryActionsFor(
environment: $environment,
connection: $connection,
latestVerificationRun: $latestVerificationRun,
surface: $surface,
includeAdminConsent: $surface !== self::SURFACE_REQUIRED_PERMISSIONS,
),
technicalDetails: $this->technicalDetails(
connection: $connection,
permissions: $permissions,
latestVerificationRun: $latestVerificationRun,
),
);
}
$verificationState = $this->verificationState($connection);
if ($errorCount > 0 || in_array($verificationState, [
ProviderVerificationStatus::Error->value,
ProviderVerificationStatus::Blocked->value,
ProviderVerificationStatus::Degraded->value,
ProviderVerificationStatus::Pending->value,
], true)) {
return $this->case(
key: 'provider_readiness.verification_failed',
severity: $verificationState === ProviderVerificationStatus::Pending->value ? 'warning' : 'danger',
status: $verificationState === ProviderVerificationStatus::Pending->value
? __('localization.provider_guidance.status_action_required')
: __('localization.provider_guidance.status_blocked'),
title: $verificationState === ProviderVerificationStatus::Pending->value
? __('localization.provider_guidance.verification_in_progress_title')
: __('localization.provider_guidance.verification_failed_title'),
reason: $this->verificationFailureReason(
verificationState: $verificationState,
connection: $connection,
errorCount: $errorCount,
),
impact: __('localization.provider_guidance.verification_failed_impact'),
primaryAction: $latestVerificationRun instanceof OperationRun
? $this->verificationOperationAction($latestVerificationRun, $environment)
: $this->providerConnectionReviewAction($environment, $connection, $surface),
secondaryActions: $this->secondaryActionsFor(
environment: $environment,
connection: $connection,
latestVerificationRun: $latestVerificationRun,
surface: $surface,
includeRequiredPermissions: true,
includeRunVerification: $surface === self::SURFACE_REQUIRED_PERMISSIONS,
),
technicalDetails: $this->technicalDetails(
connection: $connection,
permissions: $permissions,
latestVerificationRun: $latestVerificationRun,
),
);
}
$stalePermissionSnapshot = (bool) ($freshness['is_stale'] ?? true);
$lastHealthCheckAt = $connection?->last_health_check_at instanceof Carbon
? $connection->last_health_check_at
: ($connection?->last_health_check_at !== null ? Carbon::parse((string) $connection?->last_health_check_at) : null);
$verificationNotRun = $verificationState === ProviderVerificationStatus::Unknown->value || $lastHealthCheckAt === null;
$verificationStale = $lastHealthCheckAt instanceof Carbon && $lastHealthCheckAt->lt(now()->subDays(30));
if ($verificationNotRun || $verificationStale || $stalePermissionSnapshot) {
return $this->case(
key: 'provider_readiness.verification_required',
severity: 'warning',
status: __('localization.provider_guidance.status_action_required'),
title: __('localization.provider_guidance.verification_required_title'),
reason: $this->verificationRequiredReason(
verificationNotRun: $verificationNotRun,
verificationStale: $verificationStale,
stalePermissionSnapshot: $stalePermissionSnapshot,
),
impact: __('localization.provider_guidance.verification_required_impact'),
primaryAction: $this->verificationPrimaryAction(
environment: $environment,
connection: $connection,
surface: $surface,
),
secondaryActions: $this->secondaryActionsFor(
environment: $environment,
connection: $connection,
latestVerificationRun: $latestVerificationRun,
surface: $surface,
includeRequiredPermissions: true,
),
technicalDetails: $this->technicalDetails(
connection: $connection,
permissions: $permissions,
latestVerificationRun: $latestVerificationRun,
),
);
}
return $this->case(
key: 'provider_readiness.ready',
severity: 'success',
status: __('localization.provider_guidance.status_ready'),
title: __('localization.provider_guidance.ready_title'),
reason: __('localization.provider_guidance.ready_reason'),
impact: __('localization.provider_guidance.ready_impact'),
primaryAction: $this->environmentDashboardAction($environment),
secondaryActions: array_values(array_filter([
$surface === self::SURFACE_REQUIRED_PERMISSIONS ? null : $this->requiredPermissionsAction($environment),
$connection instanceof ProviderConnection ? $this->providerConnectionAction($environment, $connection, $surface) : null,
])),
technicalDetails: $this->technicalDetails(
connection: $connection,
permissions: $permissions,
latestVerificationRun: $latestVerificationRun,
),
);
}
/**
* @return array{
* counts: array{missing_application:int,missing_delegated:int,present:int,error:int},
* freshness: array{last_refreshed_at:?string,is_stale:bool}
* }
*/
private function permissionSignals(ManagedEnvironment $environment): array
{
$viewModel = $this->requiredPermissions->build($environment);
$overview = is_array($viewModel['overview'] ?? null) ? $viewModel['overview'] : [];
return [
'counts' => array_replace([
'missing_application' => 0,
'missing_delegated' => 0,
'present' => 0,
'error' => 0,
], is_array($overview['counts'] ?? null) ? $overview['counts'] : []),
'freshness' => array_replace([
'last_refreshed_at' => null,
'is_stale' => true,
], is_array($overview['freshness'] ?? null) ? $overview['freshness'] : []),
];
}
private function latestVerificationRun(ManagedEnvironment $environment, ProviderConnection $connection): ?OperationRun
{
return OperationRun::query()
->where('workspace_id', (int) $environment->workspace_id)
->where('managed_environment_id', (int) $environment->getKey())
->where('type', 'provider.connection.check')
->where('context->provider_connection_id', (int) $connection->getKey())
->orderByDesc('id')
->first();
}
private function verificationState(?ProviderConnection $connection): string
{
$state = $connection?->verification_status;
if ($state instanceof ProviderVerificationStatus) {
return $state->value;
}
if (is_string($state) && trim($state) !== '') {
return trim($state);
}
return ProviderVerificationStatus::Unknown->value;
}
private function isConsentBlocker(string $reasonCode): bool
{
return in_array($reasonCode, [
ProviderReasonCodes::ProviderConsentMissing,
ProviderReasonCodes::ProviderConsentFailed,
ProviderReasonCodes::ProviderConsentRevoked,
], true);
}
private function consentReason(string $reasonCode): string
{
return match ($reasonCode) {
ProviderReasonCodes::ProviderConsentFailed => __('localization.provider_guidance.admin_consent_failed_reason'),
ProviderReasonCodes::ProviderConsentRevoked => __('localization.provider_guidance.admin_consent_revoked_reason'),
default => __('localization.provider_guidance.admin_consent_required_reason'),
};
}
private function verificationFailureReason(
string $verificationState,
?ProviderConnection $connection,
int $errorCount,
): string {
if ($verificationState === ProviderVerificationStatus::Pending->value) {
return __('localization.provider_guidance.verification_in_progress_reason');
}
if ($verificationState === ProviderVerificationStatus::Degraded->value) {
return __('localization.provider_guidance.verification_degraded_reason');
}
if ($errorCount > 0) {
return __('localization.provider_guidance.verification_errors_reason', ['count' => $errorCount]);
}
$message = trim((string) ($connection?->last_error_message ?? ''));
if ($message !== '') {
return Str::limit($message, 180);
}
return __('localization.provider_guidance.verification_failed_reason');
}
private function verificationRequiredReason(
bool $verificationNotRun,
bool $verificationStale,
bool $stalePermissionSnapshot,
): string {
return match (true) {
$verificationNotRun => __('localization.provider_guidance.verification_not_run_reason'),
$verificationStale, $stalePermissionSnapshot => __('localization.provider_guidance.verification_stale_reason'),
default => __('localization.provider_guidance.verification_not_run_reason'),
};
}
/**
* @return array<string, mixed>
*/
private function verificationPrimaryAction(
ManagedEnvironment $environment,
?ProviderConnection $connection,
string $surface,
): array {
if ($surface === self::SURFACE_REQUIRED_PERMISSIONS) {
return [
'label' => __('localization.provider_guidance.action_run_provider_verification'),
'url' => null,
'action_name' => 'runProviderVerification',
'external' => false,
'disabled' => false,
];
}
if ($surface === self::SURFACE_PROVIDER_CONNECTIONS_VIEW || $surface === self::SURFACE_PROVIDER_CONNECTIONS_EDIT) {
return [
'label' => __('localization.provider_guidance.action_run_provider_verification'),
'url' => null,
'action_name' => 'runProviderVerification',
'external' => false,
'disabled' => false,
];
}
if ($connection instanceof ProviderConnection) {
return $this->providerConnectionAction($environment, $connection, $surface, __('localization.provider_guidance.action_open_provider_connection'));
}
return $this->providerConnectionsAction($environment);
}
/**
* @return list<array<string, mixed>>
*/
private function secondaryActionsFor(
ManagedEnvironment $environment,
?ProviderConnection $connection,
?OperationRun $latestVerificationRun,
string $surface,
bool $includeRequiredPermissions = false,
bool $includeAdminConsent = false,
bool $includeRunVerification = false,
): array {
$actions = [];
if ($includeRequiredPermissions && $surface !== self::SURFACE_REQUIRED_PERMISSIONS) {
$actions[] = $this->requiredPermissionsAction($environment);
}
if ($includeAdminConsent) {
$actions[] = $this->adminConsentAction($environment);
}
if ($latestVerificationRun instanceof OperationRun) {
$actions[] = $this->verificationOperationAction($latestVerificationRun, $environment);
}
if ($includeRunVerification && $surface === self::SURFACE_REQUIRED_PERMISSIONS) {
$actions[] = [
'label' => __('localization.provider_guidance.action_run_provider_verification'),
'url' => null,
'action_name' => 'runProviderVerification',
'external' => false,
'disabled' => false,
];
}
if ($connection instanceof ProviderConnection && ! in_array($surface, [
self::SURFACE_PROVIDER_CONNECTIONS_VIEW,
self::SURFACE_PROVIDER_CONNECTIONS_EDIT,
], true)) {
$actions[] = $this->providerConnectionAction($environment, $connection, $surface);
}
$actions[] = $this->environmentDashboardAction($environment);
return array_values(array_filter($actions, static fn (array $action): bool => ($action['url'] ?? null) !== null || ($action['action_name'] ?? null) !== null));
}
/**
* @param array{
* counts: array{missing_application:int,missing_delegated:int,present:int,error:int},
* freshness: array{last_refreshed_at:?string,is_stale:bool}
* } $permissions
* @return list<array{label:string,value:string}>
*/
private function technicalDetails(
?ProviderConnection $connection,
array $permissions,
?OperationRun $latestVerificationRun,
): array {
$counts = $permissions['counts'];
$freshness = $permissions['freshness'];
$details = [
$this->technicalDetail(
__('localization.provider_guidance.detail_missing_application_permissions_label'),
(string) ((int) ($counts['missing_application'] ?? 0)),
),
$this->technicalDetail(
__('localization.provider_guidance.detail_missing_delegated_permissions_label'),
(string) ((int) ($counts['missing_delegated'] ?? 0)),
),
$this->technicalDetail(
__('localization.provider_guidance.detail_verification_state_label'),
$this->verificationStateLabel($connection),
),
];
if (is_string($freshness['last_refreshed_at'] ?? null) && trim((string) $freshness['last_refreshed_at']) !== '') {
$details[] = $this->technicalDetail(
__('localization.provider_guidance.detail_permission_evidence_label'),
Carbon::parse((string) $freshness['last_refreshed_at'])->diffForHumans(),
);
} else {
$details[] = $this->technicalDetail(
__('localization.provider_guidance.detail_permission_evidence_label'),
__('localization.provider_guidance.verification_not_run_detail'),
);
}
if ($connection instanceof ProviderConnection) {
$consent = $connection->consent_status;
$details[] = $this->technicalDetail(
__('localization.provider_guidance.detail_consent_state_label'),
$consent instanceof ProviderConsentStatus
? Str::headline($consent->value)
: Str::headline(trim((string) $consent)),
);
}
if ($latestVerificationRun instanceof OperationRun) {
$details[] = $this->technicalDetail(
__('localization.provider_guidance.detail_last_verification_operation_label'),
OperationRunLinks::identifier($latestVerificationRun),
);
}
return $details;
}
private function verificationStateLabel(?ProviderConnection $connection): string
{
return match ($this->verificationState($connection)) {
ProviderVerificationStatus::Healthy->value => __('localization.provider_guidance.verification_ready_detail'),
ProviderVerificationStatus::Degraded->value => __('localization.provider_guidance.verification_degraded_detail'),
ProviderVerificationStatus::Blocked->value => __('localization.provider_guidance.verification_blocked_detail'),
ProviderVerificationStatus::Error->value => __('localization.provider_guidance.verification_failed_detail'),
ProviderVerificationStatus::Pending->value => __('localization.provider_guidance.verification_in_progress_detail'),
default => __('localization.provider_guidance.verification_not_run_detail'),
};
}
/**
* @return array<string, mixed>
*/
private function requiredPermissionsAction(ManagedEnvironment $environment): array
{
return [
'label' => __('localization.provider_guidance.action_open_required_permissions'),
'url' => RequiredPermissionsLinks::requiredPermissions($environment),
'action_name' => null,
'external' => false,
'disabled' => false,
];
}
/**
* @return array<string, mixed>
*/
private function adminConsentAction(ManagedEnvironment $environment): array
{
$url = RequiredPermissionsLinks::adminConsentPrimaryUrl($environment);
$directConsent = RequiredPermissionsLinks::adminConsentUrl($environment) !== null;
return [
'label' => $directConsent
? __('localization.provider_guidance.action_open_admin_consent')
: __('localization.provider_guidance.action_open_admin_consent_guide'),
'url' => $url,
'action_name' => null,
'external' => true,
'disabled' => false,
];
}
/**
* @return array<string, mixed>
*/
private function providerConnectionsAction(ManagedEnvironment $environment): array
{
return [
'label' => __('localization.provider_guidance.action_open_provider_connections'),
'url' => ManagedEnvironmentLinks::providerConnectionsUrl($environment),
'action_name' => null,
'external' => false,
'disabled' => false,
];
}
/**
* @return array<string, mixed>
*/
private function providerConnectionReviewAction(
ManagedEnvironment $environment,
?ProviderConnection $connection,
string $surface,
): array {
if ($connection instanceof ProviderConnection) {
return $this->providerConnectionAction(
environment: $environment,
connection: $connection,
surface: $surface,
label: in_array($surface, [self::SURFACE_PROVIDER_CONNECTIONS_VIEW, self::SURFACE_PROVIDER_CONNECTIONS_EDIT], true)
? __('localization.provider_guidance.action_edit_provider_connection')
: __('localization.provider_guidance.action_open_provider_connection'),
);
}
return $this->providerConnectionsAction($environment);
}
/**
* @return array<string, mixed>
*/
private function providerConnectionAction(
ManagedEnvironment $environment,
ProviderConnection $connection,
string $surface,
?string $label = null,
): array {
$page = $surface === self::SURFACE_PROVIDER_CONNECTIONS_EDIT ? 'view' : 'edit';
if ($surface === self::SURFACE_PROVIDER_CONNECTIONS_INDEX || $surface === self::SURFACE_REQUIRED_PERMISSIONS) {
$page = 'view';
}
return [
'label' => $label ?? __('localization.provider_guidance.action_open_provider_connection'),
'url' => ManagedEnvironmentLinks::providerConnectionUrl($connection, $page, $environment),
'action_name' => null,
'external' => false,
'disabled' => false,
];
}
/**
* @return array<string, mixed>
*/
private function verificationOperationAction(OperationRun $run, ManagedEnvironment $environment): array
{
return [
'label' => __('localization.provider_guidance.action_open_verification_operation'),
'url' => OperationRunLinks::view($run, $environment, CanonicalNavigationContext::fromRequest(request())),
'action_name' => null,
'external' => false,
'disabled' => false,
];
}
/**
* @return array<string, mixed>
*/
private function environmentDashboardAction(ManagedEnvironment $environment): array
{
return [
'label' => __('localization.provider_guidance.action_open_environment_dashboard'),
'url' => ManagedEnvironmentLinks::viewUrl($environment),
'action_name' => null,
'external' => false,
'disabled' => false,
];
}
/**
* @param list<array{label:string,value:string}> $technicalDetails
* @param list<array<string, mixed>> $secondaryActions
* @return array<string, mixed>
*/
private function case(
string $key,
string $severity,
string $status,
string $title,
string $reason,
string $impact,
array $primaryAction,
array $secondaryActions,
array $technicalDetails,
): array {
return [
'key' => $key,
'severity' => $severity,
'status' => $status,
'title' => $title,
'reason' => $reason,
'impact' => $impact,
'primary_action' => $primaryAction,
'secondary_actions' => array_values($secondaryActions),
'technical_details' => array_values($technicalDetails),
];
}
/**
* @return array{label:string,value:string}
*/
private function technicalDetail(string $label, string $value): array
{
return [
'label' => $label,
'value' => $value,
];
}
}