## Summary - standardize the shared verification report family across operation detail, onboarding, and tenant verification widget hosts - standardize normalized settings and normalized diff family wrappers across policy, policy version, and finding detail hosts - add parity and guard coverage plus the full Spec 197 artifacts, including recorded manual smoke evidence ## Testing - focused Sail regression pack from `specs/197-shared-detail-contract/quickstart.md` - local integrated-browser manual smoke for SC-197-003 and SC-197-004 Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #237
156 lines
5.1 KiB
PHP
156 lines
5.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Filament\Support;
|
|
|
|
final class NormalizedDiffSurface
|
|
{
|
|
/**
|
|
* @param array<string, mixed> $diff
|
|
* @return array<string, mixed>
|
|
*/
|
|
public static function build(array $diff, string $hostKind): array
|
|
{
|
|
$summary = is_array($diff['summary'] ?? null) ? $diff['summary'] : [];
|
|
$added = is_array($diff['added'] ?? null) ? $diff['added'] : [];
|
|
$removed = is_array($diff['removed'] ?? null) ? $diff['removed'] : [];
|
|
$changed = is_array($diff['changed'] ?? null) ? $diff['changed'] : [];
|
|
$message = is_string($summary['message'] ?? null) && trim((string) $summary['message']) !== ''
|
|
? trim((string) $summary['message'])
|
|
: null;
|
|
|
|
$addedCount = is_numeric($summary['added'] ?? null) ? (int) $summary['added'] : count($added);
|
|
$removedCount = is_numeric($summary['removed'] ?? null) ? (int) $summary['removed'] : count($removed);
|
|
$changedCount = is_numeric($summary['changed'] ?? null) ? (int) $summary['changed'] : count($changed);
|
|
$availabilityState = self::availabilityState($message, $addedCount, $removedCount, $changedCount);
|
|
|
|
return [
|
|
'hostKind' => $hostKind,
|
|
'availabilityState' => $availabilityState,
|
|
'summary' => [
|
|
'added' => $addedCount,
|
|
'removed' => $removedCount,
|
|
'changed' => $changedCount,
|
|
'message' => $message,
|
|
],
|
|
'viewModes' => [
|
|
['key' => 'grouped', 'label' => 'Grouped diff', 'default' => true],
|
|
],
|
|
'sectionBehavior' => [
|
|
'preservesGroupOrder' => true,
|
|
'supportsExpansion' => true,
|
|
'supportsFullscreen' => true,
|
|
],
|
|
'renderExpectations' => [
|
|
'ownsAvailabilityState' => true,
|
|
'ownsZeroDiffMessaging' => true,
|
|
'keepsHostFramingOutsideCore' => true,
|
|
],
|
|
'groups' => [
|
|
self::group('changed', 'Changed', $changed, false),
|
|
self::group('added', 'Added', $added, true),
|
|
self::group('removed', 'Removed', $removed, true),
|
|
],
|
|
'scriptRendering' => [
|
|
'policyType' => $diff['policy_type'] ?? null,
|
|
'showScriptContent' => (bool) config('tenantpilot.display.show_script_content', false),
|
|
],
|
|
'emptyState' => self::emptyState($availabilityState, $message, $addedCount, $removedCount, $changedCount),
|
|
'raw' => [
|
|
'added' => $added,
|
|
'removed' => $removed,
|
|
'changed' => $changed,
|
|
],
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $items
|
|
* @return array<string, mixed>
|
|
*/
|
|
private static function group(string $key, string $label, array $items, bool $collapsed): array
|
|
{
|
|
return [
|
|
'key' => $key,
|
|
'label' => $label,
|
|
'collapsed' => $collapsed,
|
|
'count' => count($items),
|
|
'items' => self::groupByBlock($items),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $items
|
|
* @return array<string, array<string, mixed>>
|
|
*/
|
|
private static function groupByBlock(array $items): array
|
|
{
|
|
$groups = [];
|
|
|
|
foreach ($items as $path => $value) {
|
|
if (! is_string($path) || $path === '') {
|
|
continue;
|
|
}
|
|
|
|
$parts = explode(' > ', $path, 2);
|
|
$group = count($parts) === 2 ? $parts[0] : 'Other';
|
|
$label = count($parts) === 2 ? $parts[1] : $path;
|
|
|
|
$groups[$group][$label] = $value;
|
|
}
|
|
|
|
ksort($groups);
|
|
|
|
return $groups;
|
|
}
|
|
|
|
private static function availabilityState(?string $message, int $addedCount, int $removedCount, int $changedCount): string
|
|
{
|
|
if ($message !== null && str_contains(strtolower($message), 'unavailable')) {
|
|
return 'unavailable';
|
|
}
|
|
|
|
if ($message !== null && str_contains(strtolower($message), 'partial')) {
|
|
return 'partial';
|
|
}
|
|
|
|
return 'available';
|
|
}
|
|
|
|
/**
|
|
* @return array{title: string, message: string}|null
|
|
*/
|
|
private static function emptyState(
|
|
string $availabilityState,
|
|
?string $message,
|
|
int $addedCount,
|
|
int $removedCount,
|
|
int $changedCount,
|
|
): ?array
|
|
{
|
|
if ($availabilityState === 'unavailable' && $message !== null) {
|
|
return [
|
|
'title' => 'Diff unavailable',
|
|
'message' => $message,
|
|
];
|
|
}
|
|
|
|
if ($availabilityState === 'partial' && $message !== null) {
|
|
return [
|
|
'title' => 'Diff partially available',
|
|
'message' => $message,
|
|
];
|
|
}
|
|
|
|
if ($availabilityState === 'available' && ($addedCount + $removedCount + $changedCount) === 0) {
|
|
return [
|
|
'title' => 'No normalized changes',
|
|
'message' => $message ?? 'No normalized changes were found.',
|
|
];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|