Some checks failed
PR Fast Feedback / fast-feedback (pull_request) Failing after 59s
Implemented the first version of the operator resolution guidance framework. Added new foundation classes (ResolutionCase, ResolutionAction) and a ReviewPackOutputResolutionAdapter. Updated the Customer Review Workspace and Environment Review Resource to use the new adapter. Added extensive test coverage for the framework and UI integrations.
207 lines
6.8 KiB
PHP
207 lines
6.8 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,
|
|
* 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,
|
|
* 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);
|
|
$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);
|
|
$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,
|
|
'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,
|
|
* 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',
|
|
'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;
|
|
}
|
|
}
|