diff --git a/ProcessMaker/Http/Controllers/Api/UserConfigurationController.php b/ProcessMaker/Http/Controllers/Api/UserConfigurationController.php new file mode 100644 index 0000000000..b8027af09c --- /dev/null +++ b/ProcessMaker/Http/Controllers/Api/UserConfigurationController.php @@ -0,0 +1,73 @@ + [ + 'isMenuCollapse' => true, + ], + 'cases' => [ + 'isMenuCollapse' => true, + ], + 'requests' => [ + 'isMenuCollapse' => true, + ], + 'tasks' => [ + 'isMenuCollapse' => true, + ], + ]; + + public function index() + { + $user = Auth::user(); + $query = UserConfiguration::select('user_id', 'ui_configuration'); + $query->userConfiguration($user->id); + $response = $query->first(); + + if (empty($response)) { + $response = [ + 'user_id' => $user->id, + 'ui_configuration' => json_encode(self::DEFAULT_USER_CONFIGURATION), + ]; // return default + } + + return new ApiResource($response); + } + + public function store(Request $request) + { + $user = Auth::user(); + $userConf = new UserConfiguration(); + $request->validate([ + 'ui_configuration' => 'required|array', + 'ui_configuration.launchpad' => 'required|array', + 'ui_configuration.cases' => 'required|array', + 'ui_configuration.requests' => 'required|array', + 'ui_configuration.tasks' => 'required|array', + ]); + $uiConfiguration = json_encode($request->input('ui_configuration')); + + try { + // Store the user configuration + $userConf->updateOrCreate([ + 'user_id' => $user->id, + ], [ + 'ui_configuration' => $uiConfiguration, + ]); + } catch (Exception $e) { + return response()->json(['error' => $e->getMessage()], 400); + } + + return new ApiResource($userConf->refresh()); + } +} diff --git a/ProcessMaker/Models/UserConfiguration.php b/ProcessMaker/Models/UserConfiguration.php new file mode 100644 index 0000000000..d060858695 --- /dev/null +++ b/ProcessMaker/Models/UserConfiguration.php @@ -0,0 +1,57 @@ + 'required', + ]; + } + + public function user() + { + return $this->belongsTo(User::class, 'user_id'); + } + + /** + * Get the launchpad related + */ + public function scopeUserConfiguration($query, $userId) + { + return $query->where('user_id', $userId); + } +} diff --git a/database/migrations/2024_09_26_131232_create_user_configuration.php b/database/migrations/2024_09_26_131232_create_user_configuration.php new file mode 100644 index 0000000000..5e2ba57f4e --- /dev/null +++ b/database/migrations/2024_09_26_131232_create_user_configuration.php @@ -0,0 +1,34 @@ +id(); + $table->unsignedInteger('user_id'); + $table->json('ui_configuration'); + $table->timestamps(); + + // Indexes + $table->index('user_id'); + + // Foreign keys + $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('user_configuration'); + } +}; diff --git a/routes/api.php b/routes/api.php index fcd2e070d6..c9d00de70d 100644 --- a/routes/api.php +++ b/routes/api.php @@ -35,6 +35,7 @@ use ProcessMaker\Http\Controllers\Api\TaskController; use ProcessMaker\Http\Controllers\Api\TaskDraftController; use ProcessMaker\Http\Controllers\Api\TemplateController; +use ProcessMaker\Http\Controllers\Api\UserConfigurationController; use ProcessMaker\Http\Controllers\Api\UserController; use ProcessMaker\Http\Controllers\Api\UserTokenController; use ProcessMaker\Http\Controllers\Api\WizardTemplateController; @@ -62,6 +63,9 @@ Route::get('users/{user}/tokens/{tokenId}', [UserTokenController::class, 'show'])->name('users.tokens.show'); // Permissions handled in the controller Route::post('users/{user}/tokens', [UserTokenController::class, 'store'])->name('users.tokens.store'); // Permissions handled in the controller Route::delete('users/{user}/tokens/{tokenId}', [UserTokenController::class, 'destroy'])->name('users.tokens.destroy'); // Permissions handled in the controller + // User Configuration + Route::get('users/configuration', [UserConfigurationController::class, 'index'])->name('users.configuration.index'); + Route::put('users/configuration', [UserConfigurationController::class, 'store'])->name('users.configuration.store'); // Groups//Permissions policy Route::get('groups', [GroupController::class, 'index'])->name('groups.index'); // Permissions handled in the controller diff --git a/tests/Feature/Api/UserConfigurationTest.php b/tests/Feature/Api/UserConfigurationTest.php new file mode 100644 index 0000000000..0a2b4a0c8d --- /dev/null +++ b/tests/Feature/Api/UserConfigurationTest.php @@ -0,0 +1,108 @@ +apiCall('GET', self::API_TEST_URL); + // Validate the header status code + $response->assertStatus(200); + $this->assertNotEmpty($response); + // Verify structure + $response->assertJsonStructure(self::STRUCTURE); + // Verify default values + $defaultValues = json_encode(UserConfigurationController::DEFAULT_USER_CONFIGURATION); + $this->assertEquals($response->json()['ui_configuration'], $defaultValues); + } + + /** + * Test store user configuration and get the new values + */ + public function testStoreUserConfigurationAndGetNewValues() + { + // Call the api PUT + $values = [ + 'launchpad' => [ + 'isMenuCollapse' => false, + ], + 'cases' => [ + 'isMenuCollapse' => false, + ], + 'requests' => [ + 'isMenuCollapse' => false, + ], + 'tasks' => [ + 'isMenuCollapse' => false, + ], + ]; + + $response = $this->apiCall('PUT', self::API_TEST_URL, ['ui_configuration' => $values]); + // Validate the header status code + $response->assertStatus(200); + + // Call the api GET + $response = $this->apiCall('GET', self::API_TEST_URL); + // Validate the header status code + $response->assertStatus(200); + $this->assertNotEmpty($response); + // Verify structure + $response->assertJsonStructure(self::STRUCTURE); + // Verify default values + $uiConfig = json_decode($response->json()['ui_configuration']); + $this->assertEquals($uiConfig->launchpad->isMenuCollapse, $values['launchpad']['isMenuCollapse']); + $this->assertEquals($uiConfig->cases->isMenuCollapse, $values['cases']['isMenuCollapse']); + $this->assertEquals($uiConfig->requests->isMenuCollapse, $values['requests']['isMenuCollapse']); + $this->assertEquals($uiConfig->tasks->isMenuCollapse, $values['tasks']['isMenuCollapse']); + } + + /** + * Test store user configuration with invalid values + */ + public function testStoreUserConfigurationWithInvalidValues() + { + // With no values + $response = $this->apiCall('PUT', self::API_TEST_URL); + + // Validate the header status code + $response->assertStatus(422); + $this->assertEquals('The Ui configuration field is required. (and 4 more errors)', $response->json()['message']); + + // An incomplete ui_configuration + $values = [ + 'cases' => [ + 'isMenuCollapse' => false, + ], + 'requests' => [ + 'isMenuCollapse' => false, + ], + 'tasks' => [ + 'isMenuCollapse' => false, + ], + ]; + $response = $this->apiCall('PUT', self::API_TEST_URL, ['ui_configuration' => $values]); + // Validate the header status code + $response->assertStatus(422); + $this->assertEquals('The Ui configuration.launchpad field is required.', $response->json()['message']); + } +}