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: (
-
+