浏览代码

Requirements to implement history & history support laravel-like lazy loading

moell 11 月之前
父节点
当前提交
c08ff418b9

+ 37 - 16
app/Http/Controllers/API/RequirementController.php

@@ -10,11 +10,13 @@ use App\Http\Requests\API\Requirement\BatchCreateRequest;
 use App\Http\Resources\API\AssetRequirementResource;
 use App\Http\Resources\API\RequirementResource;
 use App\Models\Asset;
+use App\Models\Enums\ActionObjectType;
 use App\Models\Enums\RequirementStatus;
 use App\Models\Enums\ObjectAction;
 use App\Models\Plan;
 use App\Models\Requirement;
 use App\Repositories\ActionRepository;
+use App\Services\History\ModelChangeDetector;
 use Illuminate\Http\Request;
 use Illuminate\Support\Facades\Auth;
 use Illuminate\Support\Facades\DB;
@@ -59,6 +61,10 @@ class RequirementController extends Controller
         ]);
         $requirement->save();
 
+        ActionRepository::createRequirement(
+            $requirement, ObjectAction::CREATED
+        );
+
         return $this->created();
     }
 
@@ -77,10 +83,15 @@ class RequirementController extends Controller
      */
     public function update(CreateOrUpdateRequest $request, string $id)
     {
-        //
-        $requiremen = Requirement::findOrFail($id);
-        $requiremen->fill($request->all());
-        $requiremen->save();
+        $requirement = Requirement::findOrFail($id);
+
+        $requirement->fill($request->all());
+        $changes = ModelChangeDetector::detector(ActionObjectType::REQUIREMENT, $requirement);
+        $requirement->save();
+
+        ActionRepository::createRequirement(
+            $requirement, ObjectAction::STARTED,objectChanges: $changes
+        );
         return $this->noContent();
     }
 
@@ -89,32 +100,42 @@ class RequirementController extends Controller
      */
     public function destroy(string $id)
     {
-        $requiremen = Requirement::findOrFail($id);
+        $requirement = Requirement::findOrFail($id);
+
+        $requirement->delete();
 
-        $requiremen->delete();
+        ActionRepository::createRequirement(
+            $requirement, ObjectAction::DELETED
+        );
 
         return $this->noContent();
     }
 
     public function close(Request $request,string $id){
-        $requiremen = Requirement::findOrFail($id);
+        $requirement = Requirement::findOrFail($id);
 
-        $requiremen->status = RequirementStatus::CLOSED->value;
-        $requiremen->save();
-
-        ActionRepository::createRequirement($requiremen, ObjectAction::CLOSED, $request->get("comment"));
+        $requirement->status = RequirementStatus::CLOSED->value;
+        $changes = ModelChangeDetector::detector(ActionObjectType::REQUIREMENT, $requirement);
+        $requirement->save();
 
+        ActionRepository::createRequirement(
+            $requirement, ObjectAction::CLOSED, $request->get("comment"), objectChanges: $changes
+        );
 
-         return $this->noContent();
+        return $this->noContent();
     }
 
     public function start(Request $request, string $id)
     {
-        $requiremen = Requirement::findOrFail($id);
-        $requiremen->status = RequirementStatus::ACTIVE->value;
-        $requiremen->save();
+        $requirement = Requirement::findOrFail($id);
+
+        $requirement->status = RequirementStatus::ACTIVE->value;
+        $changes = ModelChangeDetector::detector(ActionObjectType::REQUIREMENT, $requirement);
+        $requirement->save();
 
-        ActionRepository::createRequirement($requiremen, ObjectAction::STARTED, $request->get("comment"));
+        ActionRepository::createRequirement(
+            $requirement, ObjectAction::STARTED, $request->get("comment"), objectChanges: $changes
+        );
 
         return $this->noContent();
     }

+ 4 - 1
app/Models/Enums/ActionObjectType.php

@@ -9,6 +9,7 @@ use App\Models\Requirement;
 use App\Models\Task;
 use App\Services\History\Detector\DetectorContact;
 use App\Services\History\Detector\ProjectDetector;
+use App\Services\History\Detector\RequirementDetector;
 
 enum ActionObjectType: string
 {
@@ -37,7 +38,8 @@ enum ActionObjectType: string
     {
         return match ($this) {
             self::ASSET, self::PROJECT, self::TASK  => "name",
-            self::PLAN => "title",
+            self::PLAN, self::REQUIREMENT => "title",
+            default => "name",
         };
     }
 
@@ -45,6 +47,7 @@ enum ActionObjectType: string
     {
         return match ($this) {
             ActionObjectType::PROJECT => ProjectDetector::class,
+            ActionObjectType::REQUIREMENT => RequirementDetector::class,
             default => null
         };
     }

+ 61 - 0
app/Repositories/ActionRepository.php

@@ -189,6 +189,8 @@ class ActionRepository
 
         $objectNames = self::objectNamesGroupByType($actions);
 
+        self::eagerLoadingHistoryConverters($actionObjectType, $actions);
+
         $items = [];
         foreach ($actions as $action) {
             $item = self::actionFormat($action->toArray(), $objectNames);
@@ -200,6 +202,65 @@ class ActionRepository
         return $items;
     }
 
+    public static function eagerLoadingHistoryConverters(ActionObjectType $actionObjectType, Collection $actions)
+    {
+        $detector = $actionObjectType->detectorClassName();
+
+        $converters = [];
+        foreach(call_user_func([$detector, "converters"]) as $key => $converter) {
+            if (! $converter->isEagerLoading()) {
+                continue;
+            }
+
+            $converters[$key] = $converter;
+        }
+
+        if (! $converters) {
+            return;
+        }
+
+        $arrayFields = call_user_func([$detector, "arrayFields"]);
+        $converterFields = array_keys($converters);
+
+        $values = [];
+
+        $pushValue = function (string $field, $fieldValue) use (&$values) {
+            if (! $fieldValue) {
+                return;
+            }
+            if (is_array($fieldValue)) {
+                foreach ($fieldValue as $v) {
+                    if (in_array($v, $values[$field] ?? [])) {
+                      continue;
+                    }
+                    $values[$field][] = $v;
+                }
+            } else {
+                if (! in_array($fieldValue, $values[$field] ?? [])) {
+                    $values[$field][] = $fieldValue;
+                }
+            }
+        };
+
+        foreach ($actions as $action) {
+            foreach ($action->histories as $history) {
+                if (! in_array($history->field, $converterFields)) {
+                    continue;
+                }
+
+                $old = in_array($history->field, $arrayFields) ? json_decode($history->old, true) : $history->old;
+                $new = in_array($history->field, $arrayFields) ? json_decode($history->new, true) : $history->new;
+
+                $pushValue($history->field, $old);
+                $pushValue($history->field, $new);
+            }
+        }
+
+        foreach ($values as $key => $value) {
+            call_user_func([$converters[$key], "eagerLoad"], $value);
+        }
+    }
+
     public static function formatHistories(ActionObjectType $actionObjectType, Collection $histories): array
     {
         $detector = $actionObjectType->detectorClassName();

+ 4 - 0
app/Services/History/Converter/ConverterContact.php

@@ -5,4 +5,8 @@ namespace App\Services\History\Converter;
 interface ConverterContact
 {
     public function handle(mixed $value);
+
+    public function isEagerLoading(): bool;
+
+    public static function eagerLoad(array $items): void;
 }

+ 11 - 0
app/Services/History/Converter/EagerLoadingConverter.php

@@ -0,0 +1,11 @@
+<?php
+
+namespace App\Services\History\Converter;
+
+abstract class EagerLoadingConverter implements ConverterContact
+{
+    public function isEagerLoading(): bool
+    {
+        return true;
+    }
+}

+ 20 - 0
app/Services/History/Converter/EmailConverter.php

@@ -0,0 +1,20 @@
+<?php
+
+namespace App\Services\History\Converter;
+
+use App\Models\User;
+
+class EmailConverter extends EagerLoadingConverter implements ConverterContact
+{
+    protected static array $items;
+
+    public function handle(mixed $value)
+    {
+        return null;
+    }
+
+    public static function eagerLoad(array $items): void
+    {
+        self::$items = $items;
+    }
+}

+ 1 - 1
app/Services/History/Converter/ModelEnumConverter.php

@@ -2,7 +2,7 @@
 
 namespace App\Services\History\Converter;
 
-class ModelEnumConverter implements ConverterContact
+class ModelEnumConverter extends NotEagerLoadingConverter implements ConverterContact
 {
     public function __construct(protected string $langFieldPath)
     {}

+ 16 - 0
app/Services/History/Converter/NotEagerLoadingConverter.php

@@ -0,0 +1,16 @@
+<?php
+
+namespace App\Services\History\Converter;
+
+abstract class NotEagerLoadingConverter implements ConverterContact
+{
+    public  function isEagerLoading(): bool
+    {
+        return false;
+    }
+
+    public static function eagerLoad(array $items): void
+    {
+        // TODO: Implement eagerLoad() method.
+    }
+}

+ 6 - 1
app/Services/History/Converter/WhitelistConverter.php

@@ -4,7 +4,7 @@ namespace App\Services\History\Converter;
 
 use App\Models\User;
 
-class WhitelistConverter implements ConverterContact
+class WhitelistConverter extends EagerLoadingConverter implements ConverterContact
 {
     public function handle(mixed $value)
     {
@@ -18,4 +18,9 @@ class WhitelistConverter implements ConverterContact
 
         return $users ? implode(",", $users->toArray()) : null;
     }
+
+    public static function eagerLoad(array $items): void
+    {
+
+    }
 }

+ 30 - 0
app/Services/History/Detector/DetectorAbstract.php

@@ -0,0 +1,30 @@
+<?php
+
+namespace App\Services\History\Detector;
+
+use App\Services\History\Converter\ConverterContact;
+
+abstract class DetectorAbstract implements DetectorContact
+{
+    public static function diffFields(): array
+    {
+        return [];
+    }
+
+    public static function converter(string $field): ?ConverterContact
+    {
+        $converters = static::converters();
+
+        return $converters[$field] ?? null;
+    }
+
+    public static function converters(): array
+    {
+        return [];
+    }
+
+    public static function arrayFields(): array
+    {
+        return [];
+    }
+}

+ 5 - 1
app/Services/History/Detector/DetectorContact.php

@@ -10,5 +10,9 @@ interface DetectorContact
 
     public static function diffFields(): array;
 
-    public static function converter(string $field): ConverterContact;
+    public static function converter(string $field): ?ConverterContact;
+
+    public static function converters(): array;
+
+    public static function arrayFields(): array;
 }

+ 4 - 5
app/Services/History/Detector/ProjectDetector.php

@@ -6,7 +6,7 @@ use App\Services\History\Converter\ConverterContact;
 use App\Services\History\Converter\ModelEnumConverter;
 use App\Services\History\Converter\WhitelistConverter;
 
-class ProjectDetector implements DetectorContact
+class ProjectDetector extends DetectorAbstract
 {
     public static function fields(): array
     {
@@ -32,12 +32,11 @@ class ProjectDetector implements DetectorContact
         ];
     }
 
-    public static function converter(string $field): ConverterContact
+    public static function converters(): array
     {
-        return match ($field) {
+        return [
             "whitelist" => new WhitelistConverter(),
             "status" => new ModelEnumConverter("project.status"),
-            default => null
-        };
+        ];
     }
 }

+ 55 - 0
app/Services/History/Detector/RequirementDetector.php

@@ -0,0 +1,55 @@
+<?php
+
+namespace App\Services\History\Detector;
+
+use App\Services\History\Converter\ConverterContact;
+use App\Services\History\Converter\EmailConverter;
+use App\Services\History\Converter\ModelEnumConverter;
+use App\Services\History\Converter\WhitelistConverter;
+
+class RequirementDetector extends DetectorAbstract
+{
+    public static function fields(): array
+    {
+        return [
+            'title',
+            'status',
+            'asset_id',
+            'requirement_group_id',
+            'reviewed_by',
+            'priority',
+            'note',
+            'mailto',
+            'plan_id',
+            'comment',
+            'close_reason',
+            'description',
+            'acceptance',
+        ];
+    }
+
+    public static function diffFields(): array
+    {
+        return [
+            'description',
+            'comment',
+            'acceptance',
+        ];
+    }
+
+    public static function converters():array
+    {
+        return [
+            "whitelist" => new WhitelistConverter(),
+            "mailto" => new EmailConverter(),
+            "status" => new ModelEnumConverter("requirement.status"),
+        ];
+    }
+
+    public static function arrayFields(): array
+    {
+        return [
+            'mailto'
+        ];
+    }
+}

+ 2 - 2
app/Services/History/ModelChangeDetector.php

@@ -29,9 +29,9 @@ class ModelChangeDetector
             }
 
             $items[] = [
-                'old' => $model->getOriginal($field),
+                'old' => is_array($model->getOriginal($field)) ? json_encode($model->getOriginal($field)) : $model->getOriginal($field),
                 'field' => $field,
-                'new' => $model->$field,
+                'new' => is_array($model->$field) ? json_encode($model->$field) : $model->$field,
                 'diff' => in_array($field, $diffFields) ? text_diff($model->getOriginal($field), $model->$field) : null,
             ];
         }

+ 9 - 0
lang/en/model-enums.php

@@ -1,6 +1,7 @@
 <?php
 
 use \App\Models\Enums\ProjectStatus;
+use \App\Models\Enums\RequirementStatus;
 
 return [
     'project' => [
@@ -13,5 +14,13 @@ return [
             ProjectStatus::CANCEL->value => "Cancelled",
             ProjectStatus::PENDING_REVIEW->value => "Pending for Approval",
         ]
+    ],
+    'requirement' => [
+        'status' => [
+            RequirementStatus::DRAFT->value => "Draft",
+            RequirementStatus::CHANGED->value => "Changed",
+            RequirementStatus::ACTIVE->value => "Active",
+            RequirementStatus::CLOSED->value => "Closed",
+        ]
     ]
 ];