From 91e87f086fa3c00f10ba883087a4324a9d198f86 Mon Sep 17 00:00:00 2001 From: Miguel Angel Date: Mon, 9 Sep 2024 14:49:36 -0400 Subject: [PATCH 1/4] feat: add cases_started migration --- ...9_09_181717_create_cases_started_table.php | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 database/migrations/2024_09_09_181717_create_cases_started_table.php diff --git a/database/migrations/2024_09_09_181717_create_cases_started_table.php b/database/migrations/2024_09_09_181717_create_cases_started_table.php new file mode 100644 index 0000000000..b7b85ecf5f --- /dev/null +++ b/database/migrations/2024_09_09_181717_create_cases_started_table.php @@ -0,0 +1,44 @@ +unsignedInteger('case_number')->primary(); + $table->unsignedInteger('user_id'); + $table->string('case_title', 255); + $table->string('case_title_formatted', 255); + $table->string('case_status', 20); + $table->json('processes'); + $table->json('requests'); + $table->json('request_tokens'); + $table->json('tasks'); + $table->json('participants'); + $table->timestamp('initiated_at')->nullable(); + $table->timestamp('completed_at')->nullable(); + $table->timestamps(); + $table->text('keywords'); + + $table->foreign('user_id')->references('id')->on('users'); + $table->index(['user_id', 'case_status', 'created_at']); + $table->index(['user_id', 'case_status', 'updated_at']); + $table->index(['case_title']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('cases_started'); + } +}; From 794c160d3c0c3af2197e750e31ac6909e2b0b433 Mon Sep 17 00:00:00 2001 From: Miguel Angel Date: Mon, 9 Sep 2024 16:22:16 -0400 Subject: [PATCH 2/4] feat: add cases started model --- ProcessMaker/Models/CaseStarted.php | 45 +++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 ProcessMaker/Models/CaseStarted.php diff --git a/ProcessMaker/Models/CaseStarted.php b/ProcessMaker/Models/CaseStarted.php new file mode 100644 index 0000000000..ebb5556292 --- /dev/null +++ b/ProcessMaker/Models/CaseStarted.php @@ -0,0 +1,45 @@ + AsArrayObject::class, + 'requests' => AsArrayObject::class, + 'request_tokens' => AsArrayObject::class, + 'tasks' => AsArrayObject::class, + 'participants' => AsArrayObject::class, + 'initiated_at' => 'datetime', + 'completed_at' => 'datetime', + ]; + + /** + * Get the user that owns the case. + */ + public function user() + { + return $this->belongsTo(User::class); + } +} From 55d771c037faf39d33bcccbaa8c95e5c6157c12c Mon Sep 17 00:00:00 2001 From: Miguel Angel Date: Wed, 11 Sep 2024 15:19:15 -0400 Subject: [PATCH 3/4] feat: add new get_all_cases endpoint --- .../Controllers/Api/V1_1/CaseController.php | 200 ++++++++++++++++++ .../Http/Requests/GetAllCasesRequest.php | 34 +++ .../Http/Resources/V1_1/CaseResource.php | 34 +++ ProcessMaker/Models/CaseStarted.php | 10 + database/factories/CaseStartedFactory.php | 80 +++++++ ...9_09_181717_create_cases_started_table.php | 5 +- routes/v1_1/api.php | 8 + 7 files changed, 370 insertions(+), 1 deletion(-) create mode 100644 ProcessMaker/Http/Controllers/Api/V1_1/CaseController.php create mode 100644 ProcessMaker/Http/Requests/GetAllCasesRequest.php create mode 100644 ProcessMaker/Http/Resources/V1_1/CaseResource.php create mode 100644 database/factories/CaseStartedFactory.php diff --git a/ProcessMaker/Http/Controllers/Api/V1_1/CaseController.php b/ProcessMaker/Http/Controllers/Api/V1_1/CaseController.php new file mode 100644 index 0000000000..db95aa009f --- /dev/null +++ b/ProcessMaker/Http/Controllers/Api/V1_1/CaseController.php @@ -0,0 +1,200 @@ +get('pageSize', 15); + + $query = CaseStarted::select($this->defaultFields); + + $this->filters($request, $query); + + $pagination = CaseResource::collection($query->paginate($pageSize)); + + return [ + 'data' => $pagination->items(), + 'meta' => [ + 'total' => $pagination->total(), + 'perPage' => $pagination->perPage(), + 'currentPage' => $pagination->currentPage(), + 'lastPage' => $pagination->lastPage(), + ], + ]; + } + + /** + * Apply filters to the query. + * + * @param Request $request + * @param Builder $query + * + * @return void + */ + private function filters(Request $request, Builder $query): void + { + if ($request->has('userId')) { + $query->where('user_id', $request->get('userId')); + } + + if ($request->has('status')) { + $query->where('case_status', $request->get('status')); + } + + $this->search($request, $query); + $this->filterBy($request, $query); + $this->sortBy($request, $query); + } + + /** + * Sort the query. + * + * @param Request $request: Query parameter format: sortBy=field:asc,field2:desc,... + * @param Builder $query + * + * @return void + */ + private function sortBy(Request $request, Builder $query): void + { + $sort = explode(',', $request->get('sortBy')); + + foreach ($sort as $value) { + if (!preg_match('/^[a-zA-Z_]+:(asc|desc)$/', $value)) { + continue; + } + + $sort = explode(':', $value); + $field = $sort[0]; + $order = $sort[1] ?? 'asc'; + + if (in_array($field, $this->sortableFields)) { + $query->orderBy($field, $order); + } + } + } + + /** + * Filter the query. + * + * @param Request $request: Query parameter format: filterBy[field]=value&filterBy[field2]=value2&... + * @param Builder $query + * @param array $dateFields List of date fields in current model + * + * @return void + */ + private function filterBy(Request $request, Builder $query): void + { + if ($request->has('filterBy')) { + $filterByValue = $request->get('filterBy'); + + foreach ($filterByValue as $key => $value) { + if (!in_array($key, $this->filterableFields)) { + continue; + } + + if (in_array($key, $this->dateFields)) { + $query->whereDate($key, $value); + continue; + } + + $query->where($key, $value); + } + } + } + + /** + * Search by case number or case title. + + * @param Request $request: Query parameter format: search=keyword + * @param Builder $query + * + * @return void + */ + private function search(Request $request, Builder $query): void + { + if ($request->has('search')) { + $search = $request->get('search'); + + $query->where(function ($q) use ($search) { + foreach ($this->searchableFields as $field) { + if ($field === 'case_number') { + $q->orWhere($field, 'like', "%$search%"); + } else { + $q->orWhereFullText($field, $search . '*', ['mode' => 'boolean']); + } + } + }); + } + } +} diff --git a/ProcessMaker/Http/Requests/GetAllCasesRequest.php b/ProcessMaker/Http/Requests/GetAllCasesRequest.php new file mode 100644 index 0000000000..99365a71bb --- /dev/null +++ b/ProcessMaker/Http/Requests/GetAllCasesRequest.php @@ -0,0 +1,34 @@ +|string> + */ + public function rules(): array + { + return [ + 'userId' => 'sometimes|integer', + 'status' => 'sometimes|in:in_progress,completed', + 'sortBy' => 'sometimes|string', + 'filterBy' => 'sometimes|array', + 'search' => 'sometimes|string', + 'pageSize' => 'sometimes|integer|min:1', + 'page' => 'sometimes|integer|min:1', + ]; + } +} diff --git a/ProcessMaker/Http/Resources/V1_1/CaseResource.php b/ProcessMaker/Http/Resources/V1_1/CaseResource.php new file mode 100644 index 0000000000..993a4a0184 --- /dev/null +++ b/ProcessMaker/Http/Resources/V1_1/CaseResource.php @@ -0,0 +1,34 @@ +$field; + } + + return $data; + } +} diff --git a/ProcessMaker/Models/CaseStarted.php b/ProcessMaker/Models/CaseStarted.php index ebb5556292..41019b5c6b 100644 --- a/ProcessMaker/Models/CaseStarted.php +++ b/ProcessMaker/Models/CaseStarted.php @@ -2,11 +2,16 @@ namespace ProcessMaker\Models; +use Database\Factories\CaseStartedFactory; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Casts\AsArrayObject; +use Illuminate\Database\Eloquent\Factories\Factory; use ProcessMaker\Models\ProcessMakerModel; class CaseStarted extends ProcessMakerModel { + use HasFactory; + protected $table = 'cases_started'; protected $fillable = [ @@ -35,6 +40,11 @@ class CaseStarted extends ProcessMakerModel 'completed_at' => 'datetime', ]; + protected static function newFactory(): Factory + { + return CaseStartedFactory::new(); + } + /** * Get the user that owns the case. */ diff --git a/database/factories/CaseStartedFactory.php b/database/factories/CaseStartedFactory.php new file mode 100644 index 0000000000..a6597a9669 --- /dev/null +++ b/database/factories/CaseStartedFactory.php @@ -0,0 +1,80 @@ + + */ +class CaseStartedFactory extends Factory +{ + protected $model = CaseStarted::class; + + /** + * Define the model's default state. + * + * @return array + */ + public function definition(): array + { + return [ + 'case_number' => fake()->unique()->randomNumber(), + 'user_id' => fake()->randomElement([1, 3]), + 'case_title' => fake()->words(3, true), + 'case_title_formatted' => fake()->words(3, true), + 'case_status' => fake()->randomElement(['in_progress', 'completed']), + 'processes' => array_map(function() { + return [ + 'id' => fake()->randomNumber(), + 'name' => fake()->words(2, true), + ]; + }, range(1, 3)), + 'requests' => [ + [ + 'id' => fake()->randomNumber(), + 'name' => fake()->words(2, true), + 'parent_request' => fake()->randomNumber(), + ], + [ + 'id' => fake()->randomNumber(), + 'name' => fake()->words(3, true), + 'parent_request' => fake()->randomNumber(), + ], + ], + 'request_tokens' => fake()->randomElement([fake()->randomNumber(), fake()->randomNumber(), fake()->randomNumber()]), + 'tasks' => [ + [ + 'id' => fake()->numerify('node_####'), + 'name' => fake()->words(4, true), + ], + [ + 'id' => fake()->numerify('node_####'), + 'name' => fake()->words(3, true), + ], + [ + 'id' => fake()->numerify('node_####'), + 'name' => fake()->words(2, true), + ], + ], + 'participants' => [ + [ + 'id' => fake()->randomNumber(), + 'name' => fake()->name(), + ], + [ + 'id' => fake()->randomNumber(), + 'name' => fake()->name(), + ], + [ + 'id' => fake()->randomNumber(), + 'name' => fake()->name(), + ], + ], + 'initiated_at' => fake()->dateTime(), + 'completed_at' => fake()->dateTime(), + 'keywords' => '', + ]; + } +} diff --git a/database/migrations/2024_09_09_181717_create_cases_started_table.php b/database/migrations/2024_09_09_181717_create_cases_started_table.php index b7b85ecf5f..e3dafd2112 100644 --- a/database/migrations/2024_09_09_181717_create_cases_started_table.php +++ b/database/migrations/2024_09_09_181717_create_cases_started_table.php @@ -28,9 +28,12 @@ public function up(): void $table->text('keywords'); $table->foreign('user_id')->references('id')->on('users'); + $table->index(['user_id', 'case_status', 'created_at']); $table->index(['user_id', 'case_status', 'updated_at']); - $table->index(['case_title']); + + $table->fullText('case_title'); + $table->fullText('keywords'); }); } diff --git a/routes/v1_1/api.php b/routes/v1_1/api.php index fe1b6a01c7..f15b2d8f77 100644 --- a/routes/v1_1/api.php +++ b/routes/v1_1/api.php @@ -1,6 +1,7 @@ name('show.interstitial'); }); + + // Cases Endpoints + Route::name('cases.')->prefix('cases')->group(function () { + // Route to list all cases + Route::get('get_all_cases', [CaseController::class, 'getAllCases']) + ->name('cases.all_cases'); + }); }); From 2fc73d7c81c0a3ef6913ec1195a9c2485a0dcf90 Mon Sep 17 00:00:00 2001 From: Miguel Angel Date: Thu, 12 Sep 2024 11:12:35 -0400 Subject: [PATCH 4/4] fix cr observations --- .../Controllers/Api/V1_1/CaseController.php | 10 +++++--- .../Http/Requests/GetAllCasesRequest.php | 5 ++-- .../Http/Resources/V1_1/CaseResource.php | 3 +++ ProcessMaker/Rules/SortBy.php | 25 +++++++++++++++++++ database/factories/CaseStartedFactory.php | 2 +- ...9_09_181717_create_cases_started_table.php | 2 +- 6 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 ProcessMaker/Rules/SortBy.php diff --git a/ProcessMaker/Http/Controllers/Api/V1_1/CaseController.php b/ProcessMaker/Http/Controllers/Api/V1_1/CaseController.php index db95aa009f..301457724e 100644 --- a/ProcessMaker/Http/Controllers/Api/V1_1/CaseController.php +++ b/ProcessMaker/Http/Controllers/Api/V1_1/CaseController.php @@ -4,7 +4,6 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\Request; -use Illuminate\Http\Response; use ProcessMaker\Http\Controllers\Controller; use ProcessMaker\Http\Requests\GetAllCasesRequest; use ProcessMaker\Http\Resources\V1_1\CaseResource; @@ -12,6 +11,9 @@ class CaseController extends Controller { + /** + * Default fields used in the query select statement. + */ protected $defaultFields = [ 'case_number', 'user_id', @@ -58,6 +60,8 @@ class CaseController extends Controller 'updated_at', ]; + const DEFAULT_SORT_DIRECTION = 'asc'; + /** * Get a list of all started cases. * @@ -136,7 +140,7 @@ private function sortBy(Request $request, Builder $query): void $sort = explode(':', $value); $field = $sort[0]; - $order = $sort[1] ?? 'asc'; + $order = $sort[1] ?? self::DEFAULT_SORT_DIRECTION; if (in_array($field, $this->sortableFields)) { $query->orderBy($field, $order); @@ -189,7 +193,7 @@ private function search(Request $request, Builder $query): void $query->where(function ($q) use ($search) { foreach ($this->searchableFields as $field) { if ($field === 'case_number') { - $q->orWhere($field, 'like', "%$search%"); + $q->orWhere($field, $search); } else { $q->orWhereFullText($field, $search . '*', ['mode' => 'boolean']); } diff --git a/ProcessMaker/Http/Requests/GetAllCasesRequest.php b/ProcessMaker/Http/Requests/GetAllCasesRequest.php index 99365a71bb..500cb15bf7 100644 --- a/ProcessMaker/Http/Requests/GetAllCasesRequest.php +++ b/ProcessMaker/Http/Requests/GetAllCasesRequest.php @@ -3,6 +3,7 @@ namespace ProcessMaker\Http\Requests; use Illuminate\Foundation\Http\FormRequest; +use ProcessMaker\Rules\SortBy; class GetAllCasesRequest extends FormRequest { @@ -23,8 +24,8 @@ public function rules(): array { return [ 'userId' => 'sometimes|integer', - 'status' => 'sometimes|in:in_progress,completed', - 'sortBy' => 'sometimes|string', + 'status' => 'sometimes|in:IN_PROGRESS,COMPLETED', + 'sortBy' => ['sometimes', 'string', new SortBy], 'filterBy' => 'sometimes|array', 'search' => 'sometimes|string', 'pageSize' => 'sometimes|integer|min:1', diff --git a/ProcessMaker/Http/Resources/V1_1/CaseResource.php b/ProcessMaker/Http/Resources/V1_1/CaseResource.php index 993a4a0184..c483ecda30 100644 --- a/ProcessMaker/Http/Resources/V1_1/CaseResource.php +++ b/ProcessMaker/Http/Resources/V1_1/CaseResource.php @@ -6,6 +6,9 @@ class CaseResource extends ApiResource { + /** + * Default fields that will be included in the response. + */ protected static $defaultFields = [ 'case_number', 'user_id', diff --git a/ProcessMaker/Rules/SortBy.php b/ProcessMaker/Rules/SortBy.php new file mode 100644 index 0000000000..2fb3398141 --- /dev/null +++ b/ProcessMaker/Rules/SortBy.php @@ -0,0 +1,25 @@ + fake()->randomElement([1, 3]), 'case_title' => fake()->words(3, true), 'case_title_formatted' => fake()->words(3, true), - 'case_status' => fake()->randomElement(['in_progress', 'completed']), + 'case_status' => fake()->randomElement(['IN_PROGRESS', 'COMPLETED']), 'processes' => array_map(function() { return [ 'id' => fake()->randomNumber(), diff --git a/database/migrations/2024_09_09_181717_create_cases_started_table.php b/database/migrations/2024_09_09_181717_create_cases_started_table.php index e3dafd2112..2f9d32efe5 100644 --- a/database/migrations/2024_09_09_181717_create_cases_started_table.php +++ b/database/migrations/2024_09_09_181717_create_cases_started_table.php @@ -15,7 +15,7 @@ public function up(): void $table->unsignedInteger('case_number')->primary(); $table->unsignedInteger('user_id'); $table->string('case_title', 255); - $table->string('case_title_formatted', 255); + $table->text('case_title_formatted'); $table->string('case_status', 20); $table->json('processes'); $table->json('requests');