Simplify acceptance criteria checklist model
This commit is contained in:
+42
-165
@@ -7,24 +7,16 @@ use App\Models\Ticket;
|
||||
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
class TicketAcceptanceCriteriaData
|
||||
class AcceptanceCriteriaChecklist
|
||||
{
|
||||
public const MAX_INDENT_LEVEL = 8;
|
||||
|
||||
/**
|
||||
* @param iterable<AcceptanceCriterion> $criteria
|
||||
* @return array{
|
||||
* summary: string|null,
|
||||
* items: array<int, array{
|
||||
* checked: bool,
|
||||
* indent: int,
|
||||
* text: string
|
||||
* }>
|
||||
* }
|
||||
* @return array<int, array{checked: bool, indent: int, text: string}>
|
||||
*/
|
||||
public static function fromModels(iterable $criteria, ?string $legacyText): array
|
||||
public static function fromModels(iterable $criteria, ?string $legacyText = null): array
|
||||
{
|
||||
$summary = self::normalize($legacyText)['summary'];
|
||||
$models = ($criteria instanceof EloquentCollection ? $criteria : collect($criteria))
|
||||
->sort(function (AcceptanceCriterion $left, AcceptanceCriterion $right): int {
|
||||
return [
|
||||
@@ -43,27 +35,17 @@ class TicketAcceptanceCriteriaData
|
||||
return self::normalize($legacyText);
|
||||
}
|
||||
|
||||
/** @var array<int|null, array<int, AcceptanceCriterion>> $grouped */
|
||||
/** @var array<int|string, iterable<AcceptanceCriterion>> $grouped */
|
||||
$grouped = $models->groupBy('parent_acceptance_criterion_id')->all();
|
||||
$items = [];
|
||||
|
||||
self::flattenItems($items, $grouped, null, 0);
|
||||
|
||||
return [
|
||||
'summary' => $summary,
|
||||
'items' => $items,
|
||||
];
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* summary: string|null,
|
||||
* items: array<int, array{
|
||||
* checked: bool,
|
||||
* indent: int,
|
||||
* text: string
|
||||
* }>
|
||||
* }
|
||||
* @return array<int, array{checked: bool, indent: int, text: string}>
|
||||
*/
|
||||
public static function normalize(mixed $value): array
|
||||
{
|
||||
@@ -75,50 +57,27 @@ class TicketAcceptanceCriteriaData
|
||||
return self::normalizeFromString($value);
|
||||
}
|
||||
|
||||
return self::empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* summary: string|null,
|
||||
* items: array<int, array{
|
||||
* checked: bool,
|
||||
* indent: int,
|
||||
* text: string
|
||||
* }>
|
||||
* } $acceptanceCriteria
|
||||
*/
|
||||
public static function hasContent(array $acceptanceCriteria): bool
|
||||
{
|
||||
return $acceptanceCriteria['summary'] !== null
|
||||
|| $acceptanceCriteria['items'] !== [];
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Ticket $ticket
|
||||
* @param array{
|
||||
* summary: string|null,
|
||||
* items: array<int, array{
|
||||
* checked: bool,
|
||||
* indent: int,
|
||||
* text: string
|
||||
* }>
|
||||
* } $acceptanceCriteria
|
||||
* @param array<int, array{checked: bool, indent: int, text: string}> $items
|
||||
*/
|
||||
public static function syncTicket(Ticket $ticket, array $acceptanceCriteria): void
|
||||
public static function syncTicket(Ticket $ticket, array $items): void
|
||||
{
|
||||
$normalized = self::normalize($acceptanceCriteria);
|
||||
$normalized = self::normalize($items);
|
||||
|
||||
$ticket->acceptanceCriteria()->delete();
|
||||
|
||||
if ($normalized['items'] === []) {
|
||||
if ($normalized === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parentStack = [];
|
||||
$positionsByParent = [];
|
||||
|
||||
foreach ($normalized['items'] as $item) {
|
||||
foreach ($normalized as $item) {
|
||||
$indent = min($item['indent'], count($parentStack));
|
||||
$parentId = $indent === 0 ? null : $parentStack[$indent - 1];
|
||||
$positionKey = $parentId ?? 'root';
|
||||
@@ -140,60 +99,25 @@ class TicketAcceptanceCriteriaData
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{
|
||||
* summary: string|null,
|
||||
* items: array<int, array{
|
||||
* checked: bool,
|
||||
* indent: int,
|
||||
* text: string
|
||||
* }>
|
||||
* } $acceptanceCriteria
|
||||
* @param array<int, array{checked: bool, indent: int, text: string}> $items
|
||||
*/
|
||||
public static function toLegacyText(array $acceptanceCriteria): ?string
|
||||
public static function toText(array $items): ?string
|
||||
{
|
||||
$normalized = self::normalize($acceptanceCriteria);
|
||||
$lines = [];
|
||||
$normalized = self::normalize($items);
|
||||
|
||||
if ($normalized['summary'] !== null) {
|
||||
$lines[] = $normalized['summary'];
|
||||
if ($normalized === []) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($normalized['items'] !== []) {
|
||||
if ($normalized['summary'] !== null) {
|
||||
$lines[] = '';
|
||||
}
|
||||
|
||||
foreach ($normalized['items'] as $item) {
|
||||
$lines[] = sprintf(
|
||||
'%s- [%s] %s',
|
||||
str_repeat(' ', $item['indent']),
|
||||
$item['checked'] ? 'x' : ' ',
|
||||
$item['text'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$legacyText = trim(implode("\n", $lines));
|
||||
|
||||
return $legacyText === '' ? null : $legacyText;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* summary: string|null,
|
||||
* items: array<int, array{
|
||||
* checked: bool,
|
||||
* indent: int,
|
||||
* text: string
|
||||
* }>
|
||||
* }
|
||||
*/
|
||||
private static function empty(): array
|
||||
{
|
||||
return [
|
||||
'summary' => null,
|
||||
'items' => [],
|
||||
];
|
||||
return implode("\n", array_map(
|
||||
static fn (array $item): string => sprintf(
|
||||
'%s- [%s] %s',
|
||||
str_repeat(' ', $item['indent']),
|
||||
$item['checked'] ? 'x' : ' ',
|
||||
$item['text'],
|
||||
),
|
||||
$normalized,
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -214,23 +138,15 @@ class TicketAcceptanceCriteriaData
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $value
|
||||
* @return array{
|
||||
* summary: string|null,
|
||||
* items: array<int, array{
|
||||
* checked: bool,
|
||||
* indent: int,
|
||||
* text: string
|
||||
* }>
|
||||
* }
|
||||
* @param array<int|string, mixed> $value
|
||||
* @return array<int, array{checked: bool, indent: int, text: string}>
|
||||
*/
|
||||
private static function normalizeFromArray(array $value): array
|
||||
{
|
||||
$summary = trim((string) Arr::get($value, 'summary', ''));
|
||||
$rawItems = Arr::get($value, 'items', []);
|
||||
$rawItems = Arr::isAssoc($value) ? Arr::get($value, 'items', []) : $value;
|
||||
|
||||
if (! is_array($rawItems)) {
|
||||
$rawItems = [];
|
||||
return [];
|
||||
}
|
||||
|
||||
$items = [];
|
||||
@@ -246,78 +162,37 @@ class TicketAcceptanceCriteriaData
|
||||
continue;
|
||||
}
|
||||
|
||||
$rawChecked = Arr::get($rawItem, 'checked', false);
|
||||
$rawIndent = Arr::get($rawItem, 'indent', 0);
|
||||
|
||||
$items[] = [
|
||||
'checked' => filter_var($rawChecked, FILTER_VALIDATE_BOOLEAN),
|
||||
'indent' => self::clampIndent((int) $rawIndent),
|
||||
'checked' => filter_var(Arr::get($rawItem, 'checked', false), FILTER_VALIDATE_BOOLEAN),
|
||||
'indent' => self::clampIndent((int) Arr::get($rawItem, 'indent', 0)),
|
||||
'text' => $text,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'summary' => $summary === '' ? null : $summary,
|
||||
'items' => $items,
|
||||
];
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{
|
||||
* summary: string|null,
|
||||
* items: array<int, array{
|
||||
* checked: bool,
|
||||
* indent: int,
|
||||
* text: string
|
||||
* }>
|
||||
* }
|
||||
* @return array<int, array{checked: bool, indent: int, text: string}>
|
||||
*/
|
||||
private static function normalizeFromString(?string $value): array
|
||||
{
|
||||
$text = trim((string) $value);
|
||||
|
||||
if ($text === '') {
|
||||
return self::empty();
|
||||
return [];
|
||||
}
|
||||
|
||||
$lines = preg_split('/\R/u', $text);
|
||||
|
||||
if (! is_array($lines)) {
|
||||
return [
|
||||
'summary' => $text,
|
||||
'items' => [],
|
||||
];
|
||||
return [];
|
||||
}
|
||||
|
||||
$summary = null;
|
||||
$items = [];
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$trimmedLine = trim($line);
|
||||
|
||||
if ($trimmedLine === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($summary === null) {
|
||||
$summary = $trimmedLine;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$parsedItem = self::parseChecklistItem($line);
|
||||
|
||||
if ($parsedItem === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$items[] = $parsedItem;
|
||||
}
|
||||
|
||||
return [
|
||||
'summary' => $summary,
|
||||
'items' => $items,
|
||||
];
|
||||
return array_values(array_filter(array_map(
|
||||
static fn (string $line): ?array => self::parseChecklistItem($line),
|
||||
$lines,
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -348,6 +223,8 @@ class TicketAcceptanceCriteriaData
|
||||
$content = trim((string) Arr::get($checkboxMatches, 'text', ''));
|
||||
} elseif (preg_match('/^[-*+]\s+(?<text>.+)$/u', $content, $bulletMatches)) {
|
||||
$content = trim((string) Arr::get($bulletMatches, 'text', ''));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($content === '') {
|
||||
Reference in New Issue
Block a user