From eb354beaf1337401d3ced8067bccf33a737ff04f Mon Sep 17 00:00:00 2001 From: Truong Vo <41848815+vmtruong301296@users.noreply.github.com> Date: Mon, 16 Sep 2024 07:49:04 +0700 Subject: [PATCH 1/4] =?UTF-8?q?Remove=20Tab=20ch=E1=BB=A9c=20n=C4=83ng=20P?= =?UTF-8?q?assword?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FRONTEND/src/components/Navbar/Navbar.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/FRONTEND/src/components/Navbar/Navbar.tsx b/FRONTEND/src/components/Navbar/Navbar.tsx index fe7e008..ea11361 100755 --- a/FRONTEND/src/components/Navbar/Navbar.tsx +++ b/FRONTEND/src/components/Navbar/Navbar.tsx @@ -29,7 +29,6 @@ import { IconLogout, // IconMail, IconMoon, - IconPasswordUser, IconQrcode, IconReport, IconScan, @@ -451,7 +450,7 @@ const Navbar = ({ Change mode - setOpened(true)}> + {/* setOpened(true)}> Change password - + */} Date: Mon, 16 Sep 2024 16:20:36 +0700 Subject: [PATCH 2/4] =?UTF-8?q?T=E1=BA=A1o=20api=20trang=20criterias=20and?= =?UTF-8?q?=20test=20report?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Http/Controllers/CriteriasController.php | 191 ++++++++++++++++++ .../TestCaseForSprintController.php | 74 +++++++ BACKEND/Modules/Admin/app/Models/Criteria.php | 24 +++ BACKEND/Modules/Admin/app/Models/Sprint.php | 17 ++ .../Admin/app/Models/SprintCriteria.php | 11 + .../Admin/app/Models/TestCaseForSprint.php | 22 ++ BACKEND/Modules/Admin/app/Models/User.php | 25 +++ .../Modules/Admin/app/Models/UserCriteria.php | 11 + BACKEND/Modules/Admin/routes/api.php | 16 ++ 9 files changed, 391 insertions(+) create mode 100644 BACKEND/Modules/Admin/app/Http/Controllers/CriteriasController.php create mode 100644 BACKEND/Modules/Admin/app/Http/Controllers/TestCaseForSprintController.php create mode 100644 BACKEND/Modules/Admin/app/Models/Criteria.php create mode 100644 BACKEND/Modules/Admin/app/Models/Sprint.php create mode 100644 BACKEND/Modules/Admin/app/Models/SprintCriteria.php create mode 100644 BACKEND/Modules/Admin/app/Models/TestCaseForSprint.php create mode 100644 BACKEND/Modules/Admin/app/Models/User.php create mode 100644 BACKEND/Modules/Admin/app/Models/UserCriteria.php diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/CriteriasController.php b/BACKEND/Modules/Admin/app/Http/Controllers/CriteriasController.php new file mode 100644 index 0000000..b51ccca --- /dev/null +++ b/BACKEND/Modules/Admin/app/Http/Controllers/CriteriasController.php @@ -0,0 +1,191 @@ +find($sprintId); + // Nếu không tìm thấy sprint, trả về response lỗi + if (!$sprint) { + return AbstractController::ResultError('Sprint not found', [], 404); + } + + // Trả về thông tin sprint và criterias + return AbstractController::ResultSuccess($sprint); + } + + public function convertDataReponse($criterias) + { + $data = []; + foreach ($criterias as $key => $criteria) { + $data[$key]["criteria_name"] = $criteria->name; + $data[$key]["description"] = $criteria->description; + $data[$key]["type"] = $criteria->type; + foreach ($criteria->users as $keyChild => $user) { + $dataChild[$keyChild]["user_id"] = $user->id; + $dataChild[$keyChild]["user_name"] = $user->name; + $dataChild[$keyChild]["email"] = $user->email; + $dataChild[$keyChild]["point"] = $user->pivot->point; + $dataChild[$keyChild]["note"] = $user->pivot->note; + $dataChild[$keyChild]["sprint_id"] = $user->pivot->sprint_id; + } + $data[$key]['users'] = $dataChild; + } + + return $data; + } + /** + * Get all criterias of a user + * + * @param int $userId The id of the user + * @return \Illuminate\Database\Eloquent\Collection|null A collection of Criteria models or null if the user is not found + */ + public function getCriteriasForUser($userId) + { + $criterias = Criteria::whereHas('users', function ($query) use ($userId) { + $query->where('user_id', $userId); + })->with(['users' => function ($query) use ($userId) { + $query->where('user_id', $userId); + }])->get(); + + $data = self::convertDataReponse($criterias); + return AbstractController::ResultSuccess($data); + } + + /** + * Get all criterias of a user for a sprint + * + * @param int $userId The id of the user + * @param int $sprintId The id of the sprint + * @return \Illuminate\Database\Eloquent\Collection|null A collection of Criteria models or null if the user or sprint is not found + */ + public function getCriteriasForUserBySprint($userId, $sprintId) + { + $criterias = Criteria::whereHas('users', function ($query) use ($userId, $sprintId) { + $query->where('user_id', $userId)->where('sprint_id', $sprintId); + })->with(['users' => function ($query) use ($userId, $sprintId) { + $query->where('user_id', $userId)->where('sprint_id', $sprintId); + }])->get(); + + $data = self::convertDataReponse($criterias); + return AbstractController::ResultSuccess($data); + } + + /** + * Get all criterias + * + * @return \Illuminate\Database\Eloquent\Collection A collection of Criteria models + */ + public function getAllCriterias(Request $request) + { + if ($request->type) { + $responseData = Criteria::where('type', $request->type)->get(); + } else { + $responseData = Criteria::all(); + } + return AbstractController::ResultSuccess($responseData); + } + + /** + * Update or create multiple SprintCriteria and UserCriteria records + * + * @param int $sprintId The id of the sprint + * @param array $criteriaData An array of criteria data. Each element of the array should be an associative array with the following keys: + * - criteria_id: The id of the criteria + * - point: The point of the criteria + * - expect_result: The expected result of the criteria + * - actual_result: The actual result of the criteria + * - note: The note of the criteria + * - user_id (optional): The id of the user + * - user_point (optional): The point of the user (default is the same as point) + * - user_note (optional): The note of the user (default is the same as note) + * @return bool True if successful, false otherwise + */ + public function updateCriteriasForSprint($sprintId, Request $request) + { + $user = auth('admins')->user(); + + // Tạo hoặc lấy Sprint + $sprint = Sprint::firstOrCreate(['id' => $sprintId], [ + 'name' => 'Default Sprint Name', + 'start_date' => now(), + 'end_date' => now()->addDays(7), + 'project_id' => 1, + ]); + + $criteriaData = $request->criterias; + + foreach ($criteriaData as $data) { + // print_r($data); + $criteriaId = (int)$data['criteria_id']; + + // Kiểm tra xem criteria có tồn tại không + $criteria = Criteria::find($criteriaId); + if (!$criteria) { + // Nếu không tồn tại, có thể ném ra ngoại lệ hoặc trả về lỗi + return response()->json(['error' => 'Criteria ID ' . $criteriaId . ' does not exist'], 400); + } + + $point = $data['point']; + $expectResult = $data['expect_result']; + $actualResult = $data['actual_result']; + $note = $data['note']; + + // Cập nhật hoặc tạo mới SprintCriteria + SprintCriteria::updateOrCreate( + ['sprint_id' => $sprint->id, 'criteria_id' => $criteriaId], + ['point' => $point, 'expect_result' => $expectResult, 'actual_result' => $actualResult, 'note' => $note] + ); + + if (isset($data['users'])) { + // Xóa hết các bản ghi UserCriteria theo sprint_id và criteria_id + UserCriteria::where('criteria_id', $criteriaId) + ->where('sprint_id', $sprint->id) + ->delete(); + foreach ($data['users'] as $userData) { + $userId = $userData['user_id']; + $userPoint = $userData['point'] ?? $point; + $userNote = $userData['note'] ?? $note; + + // Chèn lại các bản ghi mới vào bảng UserCriteria + UserCriteria::create([ + 'user_id' => $userId, + 'criteria_id' => (int)$criteriaId, + 'sprint_id' => $sprint->id, + 'point' => $userPoint, + 'note' => $userNote, + 'created_by' => $user->name + ]); + } + } + } + + return AbstractController::ResultSuccess(""); + } +} diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/TestCaseForSprintController.php b/BACKEND/Modules/Admin/app/Http/Controllers/TestCaseForSprintController.php new file mode 100644 index 0000000..5ab83e8 --- /dev/null +++ b/BACKEND/Modules/Admin/app/Http/Controllers/TestCaseForSprintController.php @@ -0,0 +1,74 @@ +get(); + + if ($testReports->isEmpty()) { + return AbstractController::ResultError('No test reports found for this sprint', [], 404); + } + + return AbstractController::ResultSuccess($testReports); + } + + public function createTestReport(Request $request, $sprintId) + { + $user = auth('admins')->user(); + + $request->validate([ + 'name' => 'required|string|max:255', + 'position' => 'required|integer', + 'input' => 'required|string', + 'expect_output' => 'required|string', + 'actual_output' => 'required|string', + 'note' => 'nullable|string', + 'issue_id_on_jira' => 'nullable|string', + 'bug_id_on_jira' => 'nullable|string', + ]); + + $testReport = TestCaseForSprint::create([ + 'name' => $request->name, + 'position' => $request->position, + 'input' => $request->input, + 'expect_output' => $request->expect_output, + 'actual_output' => $request->actual_output, + 'note' => $request->note, + 'issue_id_on_jira' => $request->issue_id_on_jira, + 'bug_id_on_jira' => $request->bug_id_on_jira, + 'sprint_id' => $sprintId, + 'created_by' => $user->name, + ]); + return AbstractController::ResultSuccess($testReport); + } + + public function deleteTestReport(Request $request) + { + $id = $request->input('id'); + + $testReport = TestCaseForSprint::find($id); + + if (!$testReport) { + return AbstractController::ResultError("Test report not found", [], 404); + } + + $testReport->delete(); + return AbstractController::ResultSuccess("Test report deleted successfully"); + } +} diff --git a/BACKEND/Modules/Admin/app/Models/Criteria.php b/BACKEND/Modules/Admin/app/Models/Criteria.php new file mode 100644 index 0000000..1824091 --- /dev/null +++ b/BACKEND/Modules/Admin/app/Models/Criteria.php @@ -0,0 +1,24 @@ +belongsToMany(Sprint::class, 'sprints_criterias') + ->withPivot('point', 'expect_result', 'actual_result', 'note'); + } + + public function users() + { + return $this->belongsToMany(User::class, 'users_criterias') + ->withPivot('point', 'note', 'sprint_id') + ; + } +} diff --git a/BACKEND/Modules/Admin/app/Models/Sprint.php b/BACKEND/Modules/Admin/app/Models/Sprint.php new file mode 100644 index 0000000..2df4266 --- /dev/null +++ b/BACKEND/Modules/Admin/app/Models/Sprint.php @@ -0,0 +1,17 @@ +belongsToMany(Criteria::class, 'sprints_criterias') + ->withPivot('point', 'expect_result', 'actual_result', 'note'); + } +} \ No newline at end of file diff --git a/BACKEND/Modules/Admin/app/Models/SprintCriteria.php b/BACKEND/Modules/Admin/app/Models/SprintCriteria.php new file mode 100644 index 0000000..5b1231a --- /dev/null +++ b/BACKEND/Modules/Admin/app/Models/SprintCriteria.php @@ -0,0 +1,11 @@ +table = 'users'; + $this->guarded = []; + $this->hidden = [ + 'password', + 'forgot_code', + ]; + } +} diff --git a/BACKEND/Modules/Admin/app/Models/UserCriteria.php b/BACKEND/Modules/Admin/app/Models/UserCriteria.php new file mode 100644 index 0000000..10d3b49 --- /dev/null +++ b/BACKEND/Modules/Admin/app/Models/UserCriteria.php @@ -0,0 +1,11 @@ +middleware('check.permission:admin.hr.staff'); Route::post('/handle-ticket', [TicketController::class, 'handleTicket'])->middleware('check.permission:admin'); }); + + Route::group([ + 'prefix' => 'criterias', + ], function () { + Route::get('/sprints/{sprintId}', [CriteriasController::class, 'getCriteriasForSprint'])->middleware('check.permission:admin'); + Route::get('/users/{userId}', [CriteriasController::class, 'getCriteriasForUser'])->middleware('check.permission:admin'); + Route::get('/users/{userId}/sprints/{sprintId}', [CriteriasController::class, 'getCriteriasForUserBySprint'])->middleware('check.permission:admin'); + Route::get('/getAll', [CriteriasController::class, 'getAllCriterias'])->middleware('check.permission:admin'); + Route::post('/sprints/{sprintId}', [CriteriasController::class, 'updateCriteriasForSprint'])->middleware('check.permission:admin'); + + Route::get('/test-cases/getAll/{sprintId}', [TestCaseForSprintController::class, 'getAllReportsForSprint'])->middleware('check.permission:admin,tester'); + Route::post('/test-cases/{sprintId}', [TestCaseForSprintController::class, 'createTestReport'])->middleware('check.permission:admin,tester'); + Route::get('/test-cases/delete', [TestCaseForSprintController::class, 'deleteTestReport'])->middleware('check.permission:admin,tester'); + }); }); }); From fb47cc509dafc0d5413ac75ca595ad90d54a73ee Mon Sep 17 00:00:00 2001 From: Truong Vo <41848815+vmtruong301296@users.noreply.github.com> Date: Tue, 17 Sep 2024 10:36:17 +0700 Subject: [PATCH 3/4] =?UTF-8?q?B=E1=BB=95=20sung=20api=20cho=20ch=E1=BB=A9?= =?UTF-8?q?c=20n=C4=83ng=20profile=20and=20criterias?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/Http/Controllers/JiraController.php | 53 +++++++++++++++++-- .../Http/Controllers/ProfileController.php | 21 ++++++++ BACKEND/Modules/Admin/routes/api.php | 5 ++ BACKEND/app/Services/JiraService.php | 49 ++++++++++++----- 4 files changed, 109 insertions(+), 19 deletions(-) create mode 100644 BACKEND/Modules/Admin/app/Http/Controllers/ProfileController.php diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/JiraController.php b/BACKEND/Modules/Admin/app/Http/Controllers/JiraController.php index 47621e3..0cbea0c 100644 --- a/BACKEND/Modules/Admin/app/Http/Controllers/JiraController.php +++ b/BACKEND/Modules/Admin/app/Http/Controllers/JiraController.php @@ -193,14 +193,15 @@ class JiraController extends Controller ], 200); } - private function customSort($a, $b, $order) { + private function customSort($a, $b, $order) + { $pos_a = array_search(strtolower($a), $order); $pos_b = array_search(strtolower($b), $order); - + if ($pos_a === false || $pos_b === false) { return 0; } - + return $pos_a - $pos_b; } @@ -224,7 +225,7 @@ class JiraController extends Controller $totalTimeSpent = 0; foreach ($issue['fields']['worklog']['worklogs'] as $worklog) { $started = Carbon::parse($worklog['started']); - $totalTimeSpent = $totalTimeSpent + ($worklog['timeSpentSeconds']/60/60); + $totalTimeSpent = $totalTimeSpent + ($worklog['timeSpentSeconds'] / 60 / 60); if ($started->isSameDay($today) && $worklog['updateAuthor']['displayName'] == $user) { $filteredWorklogs[] = $worklog; } @@ -238,7 +239,7 @@ class JiraController extends Controller 'totalTimeSpent' => $totalTimeSpent, ]; $tasksByUser[$user]['assignment'] = $log['tasksAssign']; - uksort($tasksByUser[$user]["allStatus"], function($a, $b) use ($predefinedOrder) { + uksort($tasksByUser[$user]["allStatus"], function ($a, $b) use ($predefinedOrder) { return $this->customSort($a, $b, $predefinedOrder); }); } @@ -260,4 +261,46 @@ class JiraController extends Controller return response()->json(['error' => $e->getMessage()], 500); } } + + public function getDetailsProjectsById(Request $request) + { + $id = $request->input('id'); + $projects = $this->jiraService->getDetailsProjectsById($id); + return response()->json([ + 'data' => $projects, + 'status' => true + ], 200); + } + + public function getAllBoardByIdProjects(Request $request) + { + $id = $request->input('id'); + $projects = $this->jiraService->getAllBoardByIdProjects($id); + + return response()->json([ + 'data' => $projects, + 'status' => true + ], 200); + } + + public function getAllSprintByIdBoard(Request $request) + { + $id = $request->input('id'); + $projects = $this->jiraService->getAllSprintByIdBoard($id); + + return response()->json([ + 'data' => $projects, + 'status' => true + ], 200); + } + public function getAllIssueByIdSprint(Request $request) + { + $id = $request->input('id'); + $projects = $this->jiraService->getAllIssueByIdSprint($id); + + return response()->json([ + 'data' => $projects, + 'status' => true + ], 200); + } } diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/ProfileController.php b/BACKEND/Modules/Admin/app/Http/Controllers/ProfileController.php new file mode 100644 index 0000000..95f4f4c --- /dev/null +++ b/BACKEND/Modules/Admin/app/Http/Controllers/ProfileController.php @@ -0,0 +1,21 @@ +middleware('check.permission:admin.staff'); Route::get('/allocation', [JiraController::class, 'getAllUserDoing'])->middleware('check.permission:admin.staff'); diff --git a/BACKEND/app/Services/JiraService.php b/BACKEND/app/Services/JiraService.php index 75a2b34..786f43f 100644 --- a/BACKEND/app/Services/JiraService.php +++ b/BACKEND/app/Services/JiraService.php @@ -96,7 +96,7 @@ class JiraService return $issues; } - + public function getAllUsers() { $response = $this->client->get('/rest/api/3/users/search', [ @@ -233,9 +233,9 @@ class JiraService $user_warning = []; foreach ($users as $user) { $user = $user[0]; - $users_data[$user['displayName']]['user'] = $user; - $users_data[$user['displayName']]['total_spent'] = 0; - $users_data[$user['displayName']]['total_est'] = 0; + $users_data[$user['displayName']]['user'] = $user; + $users_data[$user['displayName']]['total_spent'] = 0; + $users_data[$user['displayName']]['total_est'] = 0; $body = [ 'expand' => ['names', 'schema'], 'fields' => ['summary', 'status', 'timeoriginalestimate', 'timespent', 'assignee', 'project'], @@ -246,21 +246,21 @@ class JiraService 'maxResults' => 50, 'startAt' => 0 ]; - + $response = $this->client->post('/rest/api/3/search', [ 'body' => json_encode($body) ]); - + $issues = json_decode($response->getBody()->getContents(), true); - if(count($issues['issues']) == 0){ + if (count($issues['issues']) == 0) { $user_warning[] = $user; } foreach ($issues['issues'] as $issue) { $projectName = $issue['fields']['project']['name']; $username = $issue['fields']['assignee']['displayName']; - + if (!isset($groupedIssues[$projectName])) { $groupedIssues[$projectName] = []; $groupedIssues[$projectName]['project'] = $issue['fields']['project']; @@ -272,21 +272,42 @@ class JiraService $groupedIssues[$projectName]['users'][$username]['p_total_spent'] = 0; $groupedIssues[$projectName]['users'][$username]['p_total_est'] = 0; } - + $groupedIssues[$projectName]['users'][$username]['issues'][] = $issue; $groupedIssues[$projectName]['users'][$username]['p_total_spent'] = $groupedIssues[$projectName]['users'][$username]['p_total_spent'] + $issue['fields']['timespent']; $groupedIssues[$projectName]['users'][$username]['p_total_est'] = $groupedIssues[$projectName]['users'][$username]['p_total_est'] + ($issue['fields']['timeoriginalestimate'] ?? 0); - + $users_data[$user['displayName']]['total_spent'] = $users_data[$user['displayName']]['total_spent'] + $issue['fields']['timespent']; - $users_data[$user['displayName']]['total_est'] = $users_data[$user['displayName']]['total_est'] + ($issue['fields']['timeoriginalestimate'] ?? 0); + $users_data[$user['displayName']]['total_est'] = $users_data[$user['displayName']]['total_est'] + ($issue['fields']['timeoriginalestimate'] ?? 0); } } - - - + + + return ['projects' => $groupedIssues, 'users' => $users_data, 'warningList' => $user_warning]; // return $projects; } + + public function getDetailsProjectsById($id) + { + $response = $this->client->get('/rest/api/3/project/' . $id); + return json_decode($response->getBody()->getContents(), true); + } + public function getAllBoardByIdProjects($id) + { + $response = $this->client->get('/rest/agile/1.0/board?projectKeyOrI=/' . $id); + return json_decode($response->getBody()->getContents(), true); + } + public function getAllSprintByIdBoard($id) + { + $response = $this->client->get('/rest/agile/1.0/board/' . $id . '/sprint'); + return json_decode($response->getBody()->getContents(), true); + } + public function getAllIssueByIdSprint($id) + { + $response = $this->client->get('/rest/agile/1.0/sprint/' . $id . '/issue'); + return json_decode($response->getBody()->getContents(), true); + } } From ec1221dd9972c75a6c05da98ca037dc53021f1a9 Mon Sep 17 00:00:00 2001 From: Truong Vo <41848815+vmtruong301296@users.noreply.github.com> Date: Tue, 17 Sep 2024 10:43:52 +0700 Subject: [PATCH 4/4] =?UTF-8?q?B=E1=BB=95=20sung=20=C4=91=C3=BAng=20quy?= =?UTF-8?q?=E1=BB=81n=20khi=20v=C3=A0o=20ch=E1=BB=A9c=20n=C4=83ng?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FRONTEND/src/routes/main.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/FRONTEND/src/routes/main.tsx b/FRONTEND/src/routes/main.tsx index b6642ae..b4497a9 100755 --- a/FRONTEND/src/routes/main.tsx +++ b/FRONTEND/src/routes/main.tsx @@ -151,7 +151,7 @@ const mainRoutes = [ { path: '/sprint-review', element: ( - + @@ -165,7 +165,7 @@ const mainRoutes = [ { path: '/test-report', element: ( - + @@ -193,7 +193,7 @@ const mainRoutes = [ { path: '/profile', element: ( - +