TenantAtlas/apps/platform/app/Support/ResolutionGuidance/ResolutionAction.php
Ahmed Darrazi a6ff903093
Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 3m45s
feat: review output resolve actions v1 (spec 351)
Implemented the first version of review output resolve actions. Included a ReviewOutputResolveActionMapper, commands to seed browser fixtures, updated CustomerReviewWorkspace, EnvironmentReviewResource, UI enforcement, and related views. Also added extensive unit, feature, and browser tests, and updated the design coverage matrix.
2026-06-04 02:48:18 +02:00

216 lines
7.2 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Support\ResolutionGuidance;
final class ResolutionAction
{
public const string TYPE_NAVIGATION = 'navigation';
public const string TYPE_DOWNLOAD = 'download';
public const string TYPE_DISCLOSURE = 'disclosure';
public const string TYPE_NONE = 'none';
public const string TYPE_DOMAIN_ACTION = 'domain_action';
public const string TYPE_OPERATION_ACTION = 'operation_action';
/**
* @param array{
* key?:mixed,
* label?:mixed,
* type?:mixed,
* url?:mixed,
* icon?:mixed,
* kind?:mixed,
* action_name?:mixed,
* capability?:mixed,
* requires_confirmation?:mixed,
* audit_event?:mixed,
* operation_run_type?:mixed,
* disabled_reason?:mixed
* }|null $action
* @return array{
* key:string,
* label:string,
* type:string,
* url:?string,
* icon:string,
* kind:string,
* action_name:?string,
* capability:?string,
* requires_confirmation:bool,
* audit_event:?string,
* operation_run_type:?string,
* disabled_reason:?string
* }
*/
public static function fromArray(?array $action, string $fallbackKey, string $fallbackLabel = 'Unavailable'): array
{
$label = is_string($action['label'] ?? null) && trim((string) $action['label']) !== ''
? trim((string) $action['label'])
: $fallbackLabel;
$url = is_string($action['url'] ?? null) && trim((string) $action['url']) !== ''
? trim((string) $action['url'])
: null;
$rawType = is_string($action['type'] ?? null) && trim((string) $action['type']) !== ''
? trim((string) $action['type'])
: null;
$rawKind = is_string($action['kind'] ?? null) && trim((string) $action['kind']) !== ''
? trim((string) $action['kind'])
: null;
$key = is_string($action['key'] ?? null) && trim((string) $action['key']) !== ''
? trim((string) $action['key'])
: $fallbackKey;
$type = $rawType ?? self::typeFromKind($rawKind, $url);
$actionName = is_string($action['action_name'] ?? null) && trim((string) $action['action_name']) !== ''
? trim((string) $action['action_name'])
: null;
$capability = is_string($action['capability'] ?? null) && trim((string) $action['capability']) !== ''
? trim((string) $action['capability'])
: null;
$requiresConfirmation = (bool) ($action['requires_confirmation'] ?? false);
$auditEvent = is_string($action['audit_event'] ?? null) && trim((string) $action['audit_event']) !== ''
? trim((string) $action['audit_event'])
: null;
$operationRunType = is_string($action['operation_run_type'] ?? null) && trim((string) $action['operation_run_type']) !== ''
? trim((string) $action['operation_run_type'])
: null;
if (self::isUnsafeExecutable($type, $capability, $auditEvent, $requiresConfirmation, $operationRunType)) {
$type = self::fallbackType($rawKind, $url);
$actionName = null;
$capability = null;
$requiresConfirmation = false;
$auditEvent = null;
$operationRunType = null;
}
$kind = self::kindFromType($type, $rawKind);
$icon = is_string($action['icon'] ?? null) && trim((string) $action['icon']) !== ''
? trim((string) $action['icon'])
: self::iconForType($type);
$disabledReason = is_string($action['disabled_reason'] ?? null) && trim((string) $action['disabled_reason']) !== ''
? trim((string) $action['disabled_reason'])
: null;
return [
'key' => $key,
'label' => $label,
'type' => $type,
'url' => $url,
'icon' => $icon,
'kind' => $kind,
'action_name' => $actionName,
'capability' => $capability,
'requires_confirmation' => $requiresConfirmation,
'audit_event' => $auditEvent,
'operation_run_type' => $operationRunType,
'disabled_reason' => $disabledReason,
];
}
/**
* @return array{
* key:string,
* label:string,
* type:string,
* url:null,
* icon:string,
* kind:string,
* action_name:null,
* capability:null,
* requires_confirmation:false,
* audit_event:null,
* operation_run_type:null,
* disabled_reason:?string
* }
*/
public static function none(string $key, string $label, ?string $disabledReason = null): array
{
return [
'key' => $key,
'label' => $label,
'type' => self::TYPE_NONE,
'url' => null,
'icon' => self::iconForType(self::TYPE_NONE),
'kind' => 'none',
'action_name' => null,
'capability' => null,
'requires_confirmation' => false,
'audit_event' => null,
'operation_run_type' => null,
'disabled_reason' => $disabledReason,
];
}
private static function typeFromKind(?string $kind, ?string $url): string
{
return match ($kind) {
'download' => self::TYPE_DOWNLOAD,
'disclosure' => self::TYPE_DISCLOSURE,
'none' => self::TYPE_NONE,
default => $url !== null ? self::TYPE_NAVIGATION : self::TYPE_NONE,
};
}
private static function fallbackType(?string $kind, ?string $url): string
{
return match (true) {
$kind === 'download' => self::TYPE_DOWNLOAD,
$kind === 'disclosure' => self::TYPE_DISCLOSURE,
$url !== null => self::TYPE_NAVIGATION,
default => self::TYPE_NONE,
};
}
private static function kindFromType(string $type, ?string $kind): string
{
if (is_string($kind) && $kind !== '') {
return $kind;
}
return match ($type) {
self::TYPE_DOWNLOAD => 'download',
self::TYPE_DISCLOSURE => 'disclosure',
self::TYPE_NONE => 'none',
default => 'environment_link',
};
}
private static function iconForType(string $type): string
{
return match ($type) {
self::TYPE_DOWNLOAD => 'heroicon-o-arrow-down-tray',
self::TYPE_DISCLOSURE => 'heroicon-o-information-circle',
self::TYPE_NONE => 'heroicon-o-minus-circle',
default => 'heroicon-o-arrow-top-right-on-square',
};
}
private static function isUnsafeExecutable(
string $type,
?string $capability,
?string $auditEvent,
bool $requiresConfirmation,
?string $operationRunType,
): bool {
if (! in_array($type, [self::TYPE_DOMAIN_ACTION, self::TYPE_OPERATION_ACTION], true)) {
return false;
}
if ($capability === null || $auditEvent === null) {
return true;
}
if (! $requiresConfirmation) {
return true;
}
return $type === self::TYPE_OPERATION_ACTION && $operationRunType === null;
}
}