Compare commits

...

57 commits

Author SHA1 Message Date
Peter KW
db6fdf3c29 Small design fix 2024-04-04 12:27:38 +02:00
Peter KW
5bc7aadfa3 Merge remote-tracking branch 'origin/frontend' into gruppPP 2024-04-04 12:22:55 +02:00
Peter KW
2d8d200340 Can now remove users from projects 2024-04-04 12:22:28 +02:00
Peter KW
0cb6af03e4 Merge remote-tracking branch 'origin/dev' into gruppPP 2024-04-04 12:00:55 +02:00
Davenludd
f75e6e4a6e Update useEffect dependencies in AllTimeReportsInProjectOtherUser component 2024-04-04 11:55:04 +02:00
Imbus
10d06767c4 Fix for javascript optional parameter formatting error 2024-04-04 11:55:04 +02:00
Imbus
ee49bbde69 Docs 2024-04-04 11:55:04 +02:00
Davenludd
266aaa482c Fix initial state values in TimePerRole component 2024-04-04 11:55:04 +02:00
Davenludd
194e1d52a8 Fix input fields to be read-only in OtherUsersTR and initialize state variables in TimePerRole 2024-04-04 11:55:04 +02:00
Davenludd
82e72432f6 Update API method to get all weekly reports 2024-04-04 11:55:04 +02:00
Imbus
620073f688 Logic error in getAllWeeklyReports fixed 2024-04-04 11:55:04 +02:00
Imbus
7313a27b31 Rename 2024-04-04 11:55:04 +02:00
Davenludd
d7789ab844 Refactor API call in AllTimeReportsInProjectOtherUser component 2024-04-04 11:55:04 +02:00
Imbus
4319f30799 Tests for getAllWeeklyReports 2024-04-04 11:55:03 +02:00
Imbus
2ca8c60418 TS Api for getAllWeeklyReport 2024-04-04 11:55:03 +02:00
Imbus
0a951ecd2b Rename, fix and testing for getAllWeeklyReports path 2024-04-04 11:55:03 +02:00
Peter KW
13eb6597a7 Some fixes to how they handle names and inputs 2024-04-04 11:54:34 +02:00
Peter KW
9a0f855d2b Fixes to adding members 2024-04-04 11:26:39 +02:00
Davenludd
a0ff329845 Update useEffect dependencies in AllTimeReportsInProjectOtherUser component 2024-04-03 18:36:17 +02:00
Imbus
e67c54540c RemoveUserFromProject handler implemented, corresponding TS api, untested 2024-04-03 18:08:02 +02:00
Davenludd
91671b2b63 Merge branch 'frontend' into gruppDM 2024-04-03 18:07:18 +02:00
Imbus
76ae587116 Fix for javascript optional parameter formatting error 2024-04-03 18:05:56 +02:00
Davenludd
66a064c9f8 Fix initial state values in TimePerRole component 2024-04-03 18:05:30 +02:00
Davenludd
be963b2281 Fix input fields to be read-only in OtherUsersTR and initialize state variables in TimePerRole 2024-04-03 18:05:19 +02:00
Davenludd
c0d36f8df1 Update API method to get all weekly reports 2024-04-03 18:05:07 +02:00
Imbus
f9c30c4951 Docs 2024-04-03 17:48:02 +02:00
Davenludd
520e2f74f0 Merge branch 'frontend' into gruppDM 2024-04-03 17:45:32 +02:00
Imbus
12810075f9 Logic error in getAllWeeklyReports fixed 2024-04-03 17:44:50 +02:00
Davenludd
8cc0458e1b Refactor API call in AllTimeReportsInProjectOtherUser component 2024-04-03 17:44:26 +02:00
Imbus
12a2691d55 Rename 2024-04-03 17:40:48 +02:00
Imbus
9c5aa10414 Tests for getAllWeeklyReports 2024-04-03 17:32:50 +02:00
Imbus
fcd035fe6e TS Api for getAllWeeklyReport 2024-04-03 17:32:07 +02:00
Imbus
a39cfedad3 Rename, fix and testing for getAllWeeklyReports path 2024-04-03 17:31:39 +02:00
Imbus
a1d2520d88 Updating typescript api 2024-04-03 17:12:29 +02:00
Imbus
903132e56d Polishing tests 2024-04-03 17:06:08 +02:00
Imbus
7c973009ac Adding pycache to gitignore 2024-04-03 15:54:27 +02:00
Imbus
ffe5d53625 Splitting test script 2024-04-03 15:53:52 +02:00
Imbus
8ea6dec346 Fixes for various paths 2024-04-03 15:53:36 +02:00
Imbus
61a2d1ce0c PromoteToPm handler 2024-04-03 15:53:15 +02:00
Imbus
9cca8edd9d Additional tests 2024-04-03 14:50:04 +02:00
Imbus
1f9ccca9bf Fixing integration test script 2024-04-03 14:21:55 +02:00
Davenludd
6b880244a1 Merge branch 'gruppDM' into frontend 2024-04-03 14:00:14 +02:00
Davenludd
4d7b3e0d57 Refactor API call and types in AllTimeReportsInProjectOtherUser component 2024-04-03 13:59:44 +02:00
Mattias
1b4e521508 Update variable names in TimePerActivity component 2024-04-02 18:02:40 +02:00
Mattias
a7cc48d392 Refactor TimePerRole component to use API response for time per activity 2024-04-02 18:02:04 +02:00
Davenludd
ff37236cf6 Minor fixes TimePerActivity component to use readOnly input fields 2024-04-02 17:35:02 +02:00
Davenludd
eb741ba20d Add total time calculation to DisplayUnsignedReports component 2024-04-02 17:33:20 +02:00
Davenludd
00ca5514e5 Added sign functionality to component 2024-04-02 17:27:17 +02:00
Mattias
7c7755085e Add comments for getUnsignedReportsInProject in API 2024-04-02 17:11:30 +02:00
Mattias
1e1677fc57 Refactor getUnsignedReports in DisplayUnsignedReports component 2024-04-02 17:07:47 +02:00
Mattias
0b8b430f38 Refactor DisplayUnsignedReports component to use API and WeeklyReport type 2024-04-02 17:05:55 +02:00
Mattias
93659a72dc Add getUnsignedReportsInProject API method 2024-04-02 17:05:46 +02:00
Mattias
8d6da684bf Add username retrieval from local storage 2024-04-02 16:21:23 +02:00
Davenludd
762a1b7368 Merge branch 'frontend' into gruppDM 2024-04-02 15:51:11 +02:00
Mattias
398305d3ed Fix input validation in NewWeeklyReport component 2024-04-02 15:43:18 +02:00
Mattias
6c2213b488 Update handleUpdateWeeklyReport function and fix input validation 2024-04-02 15:43:12 +02:00
Mattias
b3e363f391 Add updateWeeklyReport function to API.ts 2024-04-02 15:43:05 +02:00
31 changed files with 1144 additions and 645 deletions

1
.gitignore vendored
View file

@ -14,6 +14,7 @@ diagram.puml
backend/*.png
backend/*.jpg
backend/*.svg
__pycache__
/go.work.sum
/package-lock.json

View file

@ -108,6 +108,56 @@ const docTemplate = `{
}
}
},
"/promote/{projectName}": {
"put": {
"security": [
{
"JWT": []
}
],
"description": "Promote a user to project manager",
"consumes": [
"text/plain"
],
"produces": [
"text/plain"
],
"tags": [
"Auth"
],
"summary": "Promote to project manager",
"parameters": [
{
"type": "string",
"description": "Project name",
"name": "projectName",
"in": "path",
"required": true
},
{
"type": "string",
"description": "User name",
"name": "userName",
"in": "query",
"required": true
}
],
"responses": {
"403": {
"description": "Forbidden",
"schema": {
"type": "string"
}
},
"500": {
"description": "Internal server error",
"schema": {
"type": "string"
}
}
}
}
},
"/promoteToAdmin": {
"post": {
"security": [

View file

@ -17,6 +17,7 @@ type Database interface {
AddUser(username string, password string) error
CheckUser(username string, password string) bool
RemoveUser(username string) error
RemoveUserFromProject(username string, projectname string) error
PromoteToAdmin(username string) error
GetUserId(username string) (int, error)
AddProject(name string, description string, username string) error
@ -35,7 +36,7 @@ type Database interface {
GetProject(projectId int) (types.Project, error)
GetUserRole(username string, projectname string) (string, error)
GetWeeklyReport(username string, projectName string, week int) (types.WeeklyReport, error)
GetWeeklyReportsUser(username string, projectname string) ([]types.WeeklyReportList, error)
GetAllWeeklyReports(username string, projectname string) ([]types.WeeklyReportList, error)
GetUnsignedWeeklyReports(projectName string) ([]types.WeeklyReport, error)
SignWeeklyReport(reportId int, projectManagerId int) error
IsSiteAdmin(username string) (bool, error)
@ -86,6 +87,10 @@ const isProjectManagerQuery = `SELECT COUNT(*) > 0 FROM user_roles
JOIN projects ON user_roles.project_id = projects.id
WHERE users.username = ? AND projects.name = ? AND user_roles.p_role = 'project_manager'`
const removeUserFromProjectQuery = `DELETE FROM user_roles
WHERE user_id = (SELECT id FROM users WHERE username = ?)
AND project_id = (SELECT id FROM projects WHERE name = ?)`
// DbConnect connects to the database
func DbConnect(dbpath string) Database {
// Open the database
@ -147,6 +152,11 @@ func (d *Db) AddUserToProject(username string, projectname string, role string)
return err
}
func (d *Db) RemoveUserFromProject(username string, projectname string) error {
_, err := d.Exec(removeUserFromProjectQuery, username, projectname)
return err
}
// ChangeUserRole changes the role of a user within a project.
func (d *Db) ChangeUserRole(username string, projectname string, role string) error {
// Execute the SQL query to change the user's role
@ -463,8 +473,8 @@ func (d *Db) Migrate() error {
return nil
}
// GetWeeklyReportsUser retrieves weekly reports for a specific user and project.
func (d *Db) GetWeeklyReportsUser(username string, projectName string) ([]types.WeeklyReportList, error) {
// GetAllWeeklyReports retrieves weekly reports for a specific user and project.
func (d *Db) GetAllWeeklyReports(username string, projectName string) ([]types.WeeklyReportList, error) {
query := `
SELECT
wr.week,

View file

@ -705,7 +705,7 @@ func TestGetWeeklyReportsUser(t *testing.T) {
t.Error("AddWeeklyReport failed:", err)
}
reports, err := db.GetWeeklyReportsUser("testuser", "testproject")
reports, err := db.GetAllWeeklyReports("testuser", "testproject")
if err != nil {
t.Error("GetWeeklyReportsUser failed:", err)
}
@ -962,6 +962,5 @@ func TestRemoveProject(t *testing.T) {
if len(projects) != 0 {
t.Error("RemoveProject failed: expected 0, got", len(projects))
}
}

View file

@ -10,42 +10,33 @@ import (
// AddUserToProjectHandler is a handler that adds a user to a project with a specified role
func AddUserToProjectHandler(c *fiber.Ctx) error {
// Extract necessary parameters from the request
var requestData struct {
Username string `json:"username"`
ProjectName string `json:"projectName"`
Role string `json:"role"`
}
if err := c.BodyParser(&requestData); err != nil {
log.Info("Error parsing request body:", err)
return c.Status(400).SendString("Bad request")
}
// Check if the user adding another user to the project is a site admin
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
adminUsername := claims["name"].(string)
log.Info("Admin username from claims:", adminUsername)
pm_name := claims["name"].(string)
isAdmin, err := db.GetDb(c).IsSiteAdmin(adminUsername)
project := c.Params("projectName")
username := c.Query("userName")
// Check if the user is a project manager
isPM, err := db.GetDb(c).IsProjectManager(pm_name, project)
if err != nil {
log.Info("Error checking admin status:", err)
log.Info("Error checking if user is project manager:", err)
return c.Status(500).SendString(err.Error())
}
if !isAdmin {
log.Info("User is not a site admin:", adminUsername)
return c.Status(403).SendString("User is not a site admin")
if !isPM {
log.Info("User: ", pm_name, " is not a project manager in project: ", project)
return c.Status(403).SendString("User is not a project manager")
}
// Add the user to the project with the specified role
err = db.GetDb(c).AddUserToProject(requestData.Username, requestData.ProjectName, requestData.Role)
err = db.GetDb(c).AddUserToProject(username, project, "member")
if err != nil {
log.Info("Error adding user to project:", err)
return c.Status(500).SendString(err.Error())
}
// Return success message
log.Info("User added to project successfully:", requestData.Username)
log.Info("User : ", username, " added to project: ", project)
return c.SendStatus(fiber.StatusOK)
}

View file

@ -0,0 +1,51 @@
package projects
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
// @Summary Promote to project manager
// @Description Promote a user to project manager
// @Tags Auth
// @Security JWT
// @Accept plain
// @Produce plain
// @Param projectName path string true "Project name"
// @Param userName query string true "User name"
// @Failure 500 {string} string "Internal server error"
// @Failure 403 {string} string "Forbidden"
// @Router /promote/{projectName} [put]
//
// Login logs in a user and returns a JWT token
// Promote to project manager
func PromoteToPm(c *fiber.Ctx) error {
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
pm_name := claims["name"].(string)
project := c.Params("projectName")
new_pm_name := c.Query("userName")
// Check if the user is a project manager
isPM, err := db.GetDb(c).IsProjectManager(pm_name, project)
if err != nil {
log.Info("Error checking if user is project manager:", err)
return c.Status(500).SendString(err.Error())
}
if !isPM {
log.Info("User: ", pm_name, " is not a project manager in project: ", project)
return c.Status(403).SendString("User is not a project manager")
}
// Add the user to the project with the specified role
err = db.GetDb(c).ChangeUserRole(new_pm_name, project, "project_manager")
// Return success message
log.Info("User : ", new_pm_name, " promoted to project manager in project: ", project)
return c.SendStatus(fiber.StatusOK)
}

View file

@ -0,0 +1,40 @@
package projects
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
func RemoveUserFromProject(c *fiber.Ctx) error {
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
pm_name := claims["name"].(string)
project := c.Params("projectName")
username := c.Query("userName")
// Check if the user is a project manager
isPM, err := db.GetDb(c).IsProjectManager(pm_name, project)
if err != nil {
log.Info("Error checking if user is project manager:", err)
return c.Status(500).SendString(err.Error())
}
if !isPM {
log.Info("User: ", pm_name, " is not a project manager in project: ", project)
return c.Status(403).SendString("User is not a project manager")
}
// Remove the user from the project
if err = db.GetDb(c).RemoveUserFromProject(username, project); err != nil {
log.Info("Error removing user from project:", err)
return c.Status(500).SendString(err.Error())
}
// Return success message
log.Info("User : ", username, " removed from project: ", project)
return c.SendStatus(fiber.StatusOK)
}

View file

@ -0,0 +1,56 @@
package reports
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
// GetAllWeeklyReports retrieves all weekly reports for a user in a specific project
func GetAllWeeklyReports(c *fiber.Ctx) error {
// Extract the necessary parameters from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Extract project name and week from query parameters
projectName := c.Params("projectName")
target_user := c.Query("targetUser") // The user whose reports are being requested
// If the target user is not empty, use it as the username
if target_user == "" {
target_user = username
}
log.Info(username, " trying to get all weekly reports for: ", target_user)
if projectName == "" {
log.Info("Missing project name")
return c.Status(400).SendString("Missing project name")
}
// If the user is not a project manager, they can only view their own reports
pm, err := db.GetDb(c).IsProjectManager(username, projectName)
if err != nil {
log.Info("Error checking if user is project manager:", err)
return c.Status(500).SendString(err.Error())
}
if pm == false && target_user != username {
log.Info("Unauthorized access")
return c.Status(403).SendString("Unauthorized access")
}
// Retrieve weekly reports for the user in the project from the database
reports, err := db.GetDb(c).GetAllWeeklyReports(target_user, projectName)
if err != nil {
log.Error("Error getting weekly reports for user:", target_user, "in project:", projectName, ":", err)
return c.Status(500).SendString(err.Error())
}
log.Info("Returning weekly report")
// Return the retrieved weekly report
return c.JSON(reports)
}

View file

@ -16,11 +16,17 @@ func GetWeeklyReport(c *fiber.Ctx) error {
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
log.Info("Getting weekly report for: ", username)
// Extract project name and week from query parameters
projectName := c.Query("projectName")
week := c.Query("week")
target_user := c.Query("targetUser") // The user whose report is being requested
// If the target user is not empty, use it as the username
if target_user == "" {
target_user = username
}
log.Info(username, " trying to get weekly report for: ", target_user)
if projectName == "" || week == "" {
log.Info("Missing project name or week number")
@ -34,8 +40,20 @@ func GetWeeklyReport(c *fiber.Ctx) error {
return c.Status(400).SendString("Invalid week number")
}
// If the token user is not an admin, check if the target user is the same as the token user
pm, err := db.GetDb(c).IsProjectManager(username, projectName)
if err != nil {
log.Info("Error checking if user is project manager:", err)
return c.Status(500).SendString(err.Error())
}
if pm == false && target_user != username {
log.Info("Unauthorized access")
return c.Status(403).SendString("Unauthorized access")
}
// Call the database function to get the weekly report
report, err := db.GetDb(c).GetWeeklyReport(username, projectName, weekInt)
report, err := db.GetDb(c).GetWeeklyReport(target_user, projectName, weekInt)
if err != nil {
log.Info("Error getting weekly report from db:", err)
return c.Status(500).SendString(err.Error())

View file

@ -1,36 +0,0 @@
package reports
import (
db "ttime/internal/database"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/log"
"github.com/golang-jwt/jwt/v5"
)
// GetWeeklyReportsUserHandler retrieves all weekly reports for a user in a specific project
func GetWeeklyReportsUserHandler(c *fiber.Ctx) error {
// Extract the necessary parameters from the token
user := c.Locals("user").(*jwt.Token)
claims := user.Claims.(jwt.MapClaims)
username := claims["name"].(string)
// Extract necessary (path) parameters from the request
projectName := c.Params("projectName")
// TODO: Here we need to check whether the user is a member of the project
// If not, we should return an error. On the other hand, if the user not a member,
// the returned list of reports will (should) allways be empty.
// Retrieve weekly reports for the user in the project from the database
reports, err := db.GetDb(c).GetWeeklyReportsUser(username, projectName)
if err != nil {
log.Error("Error getting weekly reports for user:", username, "in project:", projectName, ":", err)
return c.Status(500).SendString(err.Error())
}
log.Info("Returning weekly reports for user:", username, "in project:", projectName)
// Return the list of reports as JSON
return c.JSON(reports)
}

View file

@ -119,6 +119,9 @@ func main() {
api.Get("/getUsersProject/:projectName", projects.ListAllUsersProject)
api.Post("/project", projects.CreateProject)
api.Post("/ProjectRoleChange", projects.ProjectRoleChange)
api.Put("/promoteToPm/:projectName", projects.PromoteToPm)
api.Put("/addUserToProject/:projectName", projects.AddUserToProjectHandler)
api.Delete("/removeUserFromProject/:projectName", projects.RemoveUserFromProject)
api.Delete("/removeProject/:projectName", projects.RemoveProject)
api.Delete("/project/:projectID", projects.DeleteProject)
@ -126,10 +129,9 @@ func main() {
// reportGroup := api.Group("/report") // Not currently in use
api.Get("/getWeeklyReport", reports.GetWeeklyReport)
api.Get("/getUnsignedReports/:projectName", reports.GetUnsignedReports)
api.Get("/getWeeklyReportsUser/:projectName", reports.GetWeeklyReportsUserHandler)
api.Get("/getAllWeeklyReports/:projectName", reports.GetAllWeeklyReports)
api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport)
api.Put("/signReport/:reportId", reports.SignReport)
api.Put("/addUserToProject", projects.AddUserToProjectHandler)
api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport)
// Announce the port we are listening on and start the server

View file

@ -1,8 +1,9 @@
import { NewProjMember } from "../Components/AddMember";
import { AddMemberInfo } from "../Components/AddMember";
import { ProjectRoleChange } from "../Components/ChangeRole";
import { projectTimes } from "../Components/GetProjectTimes";
import { ProjectMember } from "../Components/GetUsersInProject";
import {
UpdateWeeklyReport,
NewWeeklyReport,
NewUser,
User,
@ -87,16 +88,31 @@ interface API {
token: string,
): Promise<APIResponse<string>>;
/** Gets a weekly report for a specific user, project and week
/**
* Updates a weekly report.
* @param {UpdateWeeklyReport} weeklyReport The updated weekly report object.
* @param {string} token The authentication token.
* @returns {Promise<APIResponse<string>>} A promise containing the API response with the updated report.
*/
updateWeeklyReport(
weeklyReport: UpdateWeeklyReport,
token: string,
): Promise<APIResponse<string>>;
/** Gets a weekly report for a specific user, project and week.
* Keep in mind that the user within the token needs to be PM
* of the project to get the report, unless the user is the target user.
* @param {string} projectName The name of the project.
* @param {string} week The week number.
* @param {string} token The authentication token.
* @param {string} targetUser The username of the target user. Defaults to token user.
* @returns {Promise<APIResponse<WeeklyReport>>} A promise resolving to an API response with the retrieved report.
*/
getWeeklyReport(
projectName: string,
week: string,
token: string,
targetUser?: string,
): Promise<APIResponse<WeeklyReport>>;
/**
@ -106,9 +122,10 @@ interface API {
* @param {string} token The token of the user
* @returns {APIResponse<WeeklyReport[]>} A list of weekly reports
*/
getWeeklyReportsForUser(
getAllWeeklyReportsForUser(
projectName: string,
token: string,
targetUser?: string,
): Promise<APIResponse<WeeklyReport[]>>;
/** Gets all the projects of a user
@ -147,6 +164,17 @@ interface API {
projectName: string,
token: string,
): Promise<APIResponse<ProjectMember[]>>;
/** Gets all unsigned reports in a project.
* @param {string} projectName The name of the project.
* @param {string} token The authentication token.
* @returns {Promise<APIResponse<WeeklyReport[]>>} A promise resolving to an API response containing the list of unsigned reports.
*/
getUnsignedReportsInProject(
projectName: string,
token: string,
): Promise<APIResponse<WeeklyReport[]>>;
/**
* Changes the username of a user in the database.
* @param {StrNameChange} data The object containing the previous and new username.
@ -169,7 +197,13 @@ interface API {
): Promise<APIResponse<void>>;
addUserToProject(
user: NewProjMember,
addMemberInfo: AddMemberInfo,
token: string,
): Promise<APIResponse<void>>;
removeUserFromProject(
user: string,
project: string,
token: string,
): Promise<APIResponse<void>>;
@ -186,6 +220,19 @@ interface API {
* @param {string} token The authentication token
*/
signReport(reportId: number, token: string): Promise<APIResponse<string>>;
/**
* Promotes a user to project manager within a project.
*
* @param {string} userName The username of the user to promote
* @param {string} projectName The name of the project to promote the user in
* @returns {Promise<APIResponse<string>} A promise resolving to an API response.
*/
promoteToPm(
userName: string,
projectName: string,
token: string,
): Promise<APIResponse<string>>;
}
/** An instance of the API */
@ -295,18 +342,20 @@ export const api: API = {
},
async addUserToProject(
user: NewProjMember,
addMemberInfo: AddMemberInfo,
token: string,
): Promise<APIResponse<void>> {
try {
const response = await fetch("/api/addUserToProject", {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
const response = await fetch(
`/api/addUserToProject/${addMemberInfo.projectName}/?userName=${addMemberInfo.userName}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
},
body: JSON.stringify(user),
});
);
if (!response.ok) {
return { success: false, message: "Failed to add member" };
@ -318,6 +367,31 @@ export const api: API = {
}
},
async removeUserFromProject(
user: string,
project: string,
token: string,
): Promise<APIResponse<void>> {
try {
const response = await fetch(
`/api/removeUserFromProject/${project}?userName=${user}`,
{
method: "DELETE",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
},
);
if (!response.ok) {
return { success: false, message: "Failed to remove member" };
}
} catch (e) {
return { success: false, message: "Failed to remove member" };
}
return { success: true, message: "Removed member" };
},
async renewToken(token: string): Promise<APIResponse<string>> {
try {
const response = await fetch("/api/loginrenew", {
@ -458,14 +532,46 @@ export const api: API = {
}
},
async updateWeeklyReport(
weeklyReport: UpdateWeeklyReport,
token: string,
): Promise<APIResponse<string>> {
try {
const response = await fetch("/api/updateWeeklyReport", {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify(weeklyReport),
});
if (!response.ok) {
return {
success: false,
message: "Failed to update weekly report",
};
}
const data = await response.text();
return { success: true, message: data };
} catch (e) {
return {
success: false,
message: "Failed to update weekly report",
};
}
},
async getWeeklyReport(
projectName: string,
week: string,
token: string,
targetUser?: string,
): Promise<APIResponse<WeeklyReport>> {
try {
const response = await fetch(
`/api/getWeeklyReport?projectName=${projectName}&week=${week}`,
`/api/getWeeklyReport?projectName=${projectName}&week=${week}&targetUser=${targetUser ?? ""}`,
{
method: "GET",
headers: {
@ -486,18 +592,22 @@ export const api: API = {
}
},
async getWeeklyReportsForUser(
async getAllWeeklyReportsForUser(
projectName: string,
token: string,
targetUser?: string,
): Promise<APIResponse<WeeklyReport[]>> {
try {
const response = await fetch(`/api/getWeeklyReportsUser/${projectName}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
const response = await fetch(
`/api/getAllWeeklyReports/${projectName}?targetUser=${targetUser ?? ""}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
},
});
);
if (!response.ok) {
return {
@ -621,6 +731,38 @@ export const api: API = {
}
},
async getUnsignedReportsInProject(
projectName: string,
token: string,
): Promise<APIResponse<WeeklyReport[]>> {
try {
const response = await fetch(`/api/getUnsignedReports/${projectName}`, {
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
});
if (!response.ok) {
return {
success: false,
message:
"Failed to get unsigned reports for project: Response code " +
response.status,
};
} else {
const data = (await response.json()) as WeeklyReport[];
return { success: true, data };
}
} catch (e) {
return {
success: false,
message: "Failed to get unsigned reports for project, unknown error",
};
}
},
async changeUserName(
data: StrNameChange,
token: string,
@ -697,4 +839,35 @@ export const api: API = {
return { success: false, message: "Failed to sign report" };
}
},
async promoteToPm(
userName: string,
projectName: string,
token: string,
): Promise<APIResponse<string>> {
try {
const response = await fetch(
`/api/promoteToPm/${projectName}?userName=${userName}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
},
);
if (!response.ok) {
return {
success: false,
message: "Failed to promote user to project manager",
};
}
} catch (e) {
return {
success: false,
message: "Failed to promote user to project manager",
};
}
return { success: true, message: "User promoted to project manager" };
},
};

View file

@ -1,44 +1,35 @@
import { APIResponse, api } from "../API/API";
import { api } from "../API/API";
export interface NewProjMember {
username: string;
role: string;
projectname: string;
export interface AddMemberInfo {
userName: string;
projectName: string;
}
/**
* Tries to add a member to a project
* @param {Object} props - A NewProjMember
* @returns {boolean} True if added, false if not
* @param {AddMemberInfo} props.membertoAdd - Contains user's name and project's name
* @returns {Promise<void>}
*/
function AddMember(props: { memberToAdd: NewProjMember }): boolean {
let added = false;
if (
props.memberToAdd.username === "" ||
props.memberToAdd.role === "" ||
props.memberToAdd.projectname === ""
) {
alert("All fields must be filled before adding");
return added;
async function AddMember(props: { memberToAdd: AddMemberInfo }): Promise<void> {
if (props.memberToAdd.userName === "") {
alert("You must choose at least one user to add");
return;
}
api
.addUserToProject(
try {
const response = await api.addUserToProject(
props.memberToAdd,
localStorage.getItem("accessToken") ?? "",
)
.then((response: APIResponse<void>) => {
if (response.success) {
alert("Member added");
added = true;
} else {
alert("Member not added");
console.error(response.message);
}
})
.catch((error) => {
console.error("An error occurred during member add:", error);
});
return added;
);
if (response.success) {
alert(`[${props.memberToAdd.userName}] added`);
} else {
alert(`[${props.memberToAdd.userName}] not added`);
console.error(response.message);
}
} catch (error) {
alert(`[${props.memberToAdd.userName}] not added`);
console.error("An error occurred during member add:", error);
}
}
export default AddMember;

View file

@ -1,37 +1,10 @@
import { useState } from "react";
import { APIResponse, api } from "../API/API";
import { api } from "../API/API";
import { NewProject } from "../Types/goTypes";
import InputField from "./InputField";
import Logo from "../assets/Logo.svg";
import Button from "./Button";
/**
* Tries to add a project to the system
* @param {Object} props - Project name and description
* @returns {boolean} True if created, false if not
*/
function CreateProject(props: { name: string; description: string }): void {
const project: NewProject = {
name: props.name,
description: props.description,
};
api
.createProject(project, localStorage.getItem("accessToken") ?? "")
.then((response: APIResponse<void>) => {
if (response.success) {
alert("Project added!");
} else {
alert("Project NOT added!");
console.error(response.message);
}
})
.catch((error) => {
alert("Project NOT added!");
console.error("An error occurred during creation:", error);
});
}
/**
* Provides UI for adding a project to the system.
* @returns {JSX.Element} - Returns the component UI for adding a project
@ -40,6 +13,33 @@ function AddProject(): JSX.Element {
const [name, setName] = useState("");
const [description, setDescription] = useState("");
/**
* Tries to add a project to the system
*/
const handleCreateProject = async (): Promise<void> => {
const project: NewProject = {
name: name.replace(/ /g, ""),
description: description.trim(),
};
try {
const response = await api.createProject(
project,
localStorage.getItem("accessToken") ?? "",
);
if (response.success) {
alert(`${project.name} added!`);
setDescription("");
setName("");
} else {
alert("Project not added, name could be taken");
console.error(response.message);
}
} catch (error) {
alert("Project not added");
console.error(error);
}
};
return (
<div className="flex flex-col h-fit w-screen items-center justify-center">
<div className="border-4 border-black bg-white flex flex-col items-center justify-center h-fit w-fit rounded-3xl content-center pl-20 pr-20">
@ -47,10 +47,7 @@ function AddProject(): JSX.Element {
className="bg-white rounded px-8 pt-6 pb-8 mb-4 items-center justify-center flex flex-col w-fit h-fit"
onSubmit={(e) => {
e.preventDefault();
CreateProject({
name: name,
description: description,
});
void handleCreateProject();
}}
>
<img

View file

@ -1,71 +1,86 @@
import { useState } from "react";
import { useEffect, useState } from "react";
import Button from "./Button";
import GetAllUsers from "./GetAllUsers";
import AddMember, { NewProjMember } from "./AddMember";
import AddMember, { AddMemberInfo } from "./AddMember";
import BackButton from "./BackButton";
import GetUsersInProject, { ProjectMember } from "./GetUsersInProject";
import GetAllUsers from "./GetAllUsers";
/**
* Provides UI for adding a member to a project.
* @returns {JSX.Element} - Returns the component UI for adding a member
*/
function AddUserToProject(props: { projectName: string }): JSX.Element {
const [name, setName] = useState("");
const [names, setNames] = useState<string[]>([]);
const [users, setUsers] = useState<string[]>([]);
const [role, setRole] = useState("");
GetAllUsers({ setUsersProp: setUsers });
const [usersProj, setUsersProj] = useState<ProjectMember[]>([]);
const handleClick = (): boolean => {
const newMember: NewProjMember = {
username: name,
projectname: props.projectName,
role: role,
};
return AddMember({ memberToAdd: newMember });
// Gets all users and project members for filtering
GetAllUsers({ setUsersProp: setUsers });
GetUsersInProject({
setUsersProp: setUsersProj,
projectName: props.projectName,
});
/*
* Filters the members from users so that users who are already
* members are not shown
*/
useEffect(() => {
setUsers((prevUsers) => {
const filteredUsers = prevUsers.filter(
(user) =>
!usersProj.some((projectUser) => projectUser.Username === user),
);
return filteredUsers;
});
}, [usersProj]);
// Attempts to add all of the selected users to the project
const handleAddClick = async (): Promise<void> => {
if (names.length === 0)
alert("You have to choose at least one user to add");
for (const name of names) {
const newMember: AddMemberInfo = {
userName: name,
projectName: props.projectName,
};
await AddMember({ memberToAdd: newMember });
}
setNames([]);
location.reload();
};
// Updates the names that have been selected
const handleUserClick = (user: string): void => {
setNames((prevNames): string[] => {
if (!prevNames.includes(user)) {
return [...prevNames, user];
}
return prevNames.filter((name) => name !== user);
});
};
return (
<div className="border-4 border-black bg-white flex flex-col items-center justify-center rounded-3xl content-center pl-20 pr-20 h-[75vh] w-[50vh]">
<p className="pb-4 mb-2 text-center font-bold text-[18px]">
User chosen: [{name}]
<div className="border-4 border-black bg-white flex flex-col items-center pt-10 rounded-3xl content-center pl-20 pr-20 h-[63vh] w-[50] overflow-auto">
<h1 className="text-center font-bold text-[36px] pb-10">
{props.projectName}
</h1>
<p className="p-1 text-center font-bold text-[26px]">
Choose users to add:
</p>
<p className="pb-4 mb-2 text-center font-bold text-[18px]">
Role chosen: [{role}]
</p>
<p className="pb-4 mb-2 text-center font-bold text-[18px]">
Project chosen: [{props.projectName}]
</p>
<p className="p-1">Choose role:</p>
<div className="border-2 border-black p-2 rounded-xl text-center h-[10h] w-[16] overflow-auto">
<ul className="text-center items-center font-medium space-y-2">
<li
className="h-[10] w-[14] items-start px-2 py-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer"
onClick={() => {
setRole("member");
}}
>
{"Member"}
</li>
<li
className="h-[10] w-[14] items-start px-2 py-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer"
onClick={() => {
setRole("project_manager");
}}
>
{"Project manager"}
</li>
</ul>
</div>
<p className="p-1">Choose user:</p>
<div className="border-2 border-black p-2 rounded-xl text-center overflow-scroll h-[26vh] w-[26vh]">
<div className="border-2 border-black pl-2 pr-2 pb-2 rounded-xl text-center overflow-auto h-[26vh] w-[26vh]">
<ul className="text-center font-medium space-y-2">
<div></div>
{users.map((user) => (
<li
className="items-start p-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-600 hover:text-slate-100 hover:cursor-pointer"
className={
names.includes(user)
? "items-start p-1 border-2 border-transparent rounded-full bg-orange-500 hover:bg-orange-600 text-white hover:cursor-pointer ring-2 ring-black"
: "items-start p-1 border-2 border-black rounded-full bg-orange-200 hover:bg-orange-400 hover:text-slate-100 hover:cursor-pointer"
}
key={user}
value={user}
onClick={() => {
setName(user);
handleUserClick(user);
}}
>
<span>{user}</span>
@ -73,13 +88,16 @@ function AddUserToProject(props: { projectName: string }): JSX.Element {
))}
</ul>
</div>
<div className="flex space-x-5 items-center justify-between">
<p className="pt-10 pb-5 underline text-center font-bold text-[18px]">
Number of users to be added: {names.length}
</p>
<div className="space-x-10 items-center">
<Button
text="Add"
onClick={(): void => {
handleClick();
void handleAddClick();
}}
type="submit"
type="button"
/>
<BackButton />
</div>

View file

@ -17,7 +17,7 @@ function AllTimeReportsInProject(): JSX.Element {
useEffect(() => {
const getWeeklyReports = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getWeeklyReportsForUser(
const response = await api.getAllWeeklyReportsForUser(
projectName ?? "",
token,
);

View file

@ -1,8 +1,9 @@
//Info: This component is used to display all the time reports for a project. It will display the week number,
//total time spent, and if the report has been signed or not. The user can click on a report to edit it.
import { useEffect, useState } from "react";
import { NewWeeklyReport } from "../Types/goTypes";
import { WeeklyReport } from "../Types/goTypes";
import { Link, useParams } from "react-router-dom";
import { api } from "../API/API";
/**
* Renders a component that displays all the time reports for a specific project.
@ -11,15 +12,15 @@ import { Link, useParams } from "react-router-dom";
function AllTimeReportsInProject(): JSX.Element {
const { username } = useParams();
const { projectName } = useParams();
const [weeklyReports, setWeeklyReports] = useState<NewWeeklyReport[]>([]);
const [weeklyReports, setWeeklyReports] = useState<WeeklyReport[]>([]);
/* // Call getProjects when the component mounts
useEffect(() => {
const getWeeklyReports = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getWeeklyReportsForUser(
const response = await api.getAllWeeklyReportsForUser(
projectName ?? "",
token,
username ?? "",
);
console.log(response);
if (response.success) {
@ -27,41 +28,10 @@ function AllTimeReportsInProject(): JSX.Element {
} else {
console.error(response.message);
}
}; */
// Mock data
const getWeeklyReports = async (): Promise<void> => {
// Simulate a delay
await Promise.resolve();
const mockWeeklyReports: NewWeeklyReport[] = [
{
projectName: "Project 1",
week: 1,
developmentTime: 10,
meetingTime: 2,
adminTime: 1,
ownWorkTime: 3,
studyTime: 4,
testingTime: 5,
},
{
projectName: "Project 1",
week: 2,
developmentTime: 8,
meetingTime: 2,
adminTime: 1,
ownWorkTime: 3,
studyTime: 4,
testingTime: 5,
},
// Add more reports as needed
];
};
// Use the mock data instead of the real data
setWeeklyReports(mockWeeklyReports);
};
useEffect(() => {
void getWeeklyReports();
}, []);
}, [projectName, username]);
return (
<>

View file

@ -2,7 +2,7 @@ import { useState } from "react";
import Button from "./Button";
import ChangeRole, { ProjectRoleChange } from "./ChangeRole";
export default function ChangeRoles(props: {
export default function ChangeRoleView(props: {
projectName: string;
username: string;
}): JSX.Element {

View file

@ -2,8 +2,11 @@ import { APIResponse, api } from "../API/API";
import { StrNameChange } from "../Types/goTypes";
function ChangeUsername(props: { nameChange: StrNameChange }): void {
if (props.nameChange.newName === "") {
alert("You have to select a new name");
if (
props.nameChange.newName === "" ||
props.nameChange.newName === props.nameChange.prevName
) {
alert("You have to give a new name\n\nName not changed");
return;
}
api
@ -13,7 +16,7 @@ function ChangeUsername(props: { nameChange: StrNameChange }): void {
alert("Name changed successfully");
location.reload();
} else {
alert("Name not changed");
alert("Name not changed, name could be taken");
console.error(response.message);
}
})

View file

@ -1,12 +1,7 @@
import { useState, useEffect } from "react";
import { Link, useParams } from "react-router-dom";
interface UnsignedReports {
projectName: string;
username: string;
week: number;
signed: boolean;
}
import { api } from "../API/API";
import { WeeklyReport } from "../Types/goTypes";
/**
* Renders a component that displays the projects a user is a part of and links to the projects start-page.
@ -14,80 +9,25 @@ interface UnsignedReports {
*/
function DisplayUserProject(): JSX.Element {
const { projectName } = useParams();
const [unsignedReports, setUnsignedReports] = useState<UnsignedReports[]>([]);
const [unsignedReports, setUnsignedReports] = useState<WeeklyReport[]>([]);
//const navigate = useNavigate();
// const getUnsignedReports = async (): Promise<void> => {
// const token = localStorage.getItem("accessToken") ?? "";
// const response = await api.getUserProjects(token);
// console.log(response);
// if (response.success) {
// setUnsignedReports(response.data ?? []);
// } else {
// console.error(response.message);
// }
// };
// const handleReportClick = async (projectName: string): Promise<void> => {
// const username = localStorage.getItem("username") ?? "";
// const token = localStorage.getItem("accessToken") ?? "";
// const response = await api.checkIfProjectManager(
// username,
// projectName,
// token,
// );
// if (response.success) {
// if (response.data) {
// navigate(`/PMProjectPage/${projectName}`);
// } else {
// navigate(`/project/${projectName}`);
// }
// } else {
// // handle error
// console.error(response.message);
// }
// };
const getUnsignedReports = async (): Promise<void> => {
// Simulate a delay
await Promise.resolve();
// Use mock data
const reports: UnsignedReports[] = [
{
projectName: "projecttest",
username: "user1",
week: 2,
signed: false,
},
{
projectName: "projecttest",
username: "user2",
week: 2,
signed: false,
},
{
projectName: "projecttest",
username: "user3",
week: 2,
signed: false,
},
{
projectName: "projecttest",
username: "user4",
week: 2,
signed: false,
},
];
// Set the state with the mock data
setUnsignedReports(reports);
};
// Call getProjects when the component mounts
useEffect(() => {
const getUnsignedReports = async (): Promise<void> => {
const token = localStorage.getItem("accessToken") ?? "";
const response = await api.getUnsignedReportsInProject(
projectName ?? "",
token,
);
console.log(response);
if (response.success) {
setUnsignedReports(response.data ?? []);
} else {
console.error(response.message);
}
};
void getUnsignedReports();
}, []);
}, [projectName]); // Include 'projectName' in the dependency array
return (
<>
@ -95,32 +35,40 @@ function DisplayUserProject(): JSX.Element {
All Unsigned Reports In: {projectName}{" "}
</h1>
<div className="border-4 border-black bg-white flex flex-col items-center justify-center min-h-[65vh] h-fit w-[70vw] rounded-3xl content-center overflow-scroll space-y-[10vh] p-[30px] text-[20px]">
{unsignedReports.map(
(unsignedReport: UnsignedReports, index: number) => (
<h1 key={index} className="border-b-2 border-black w-full">
<div className="flex justify-between">
<div className="flex">
<h1>{unsignedReport.username}</h1>
<span className="ml-6 mr-2 font-bold">Week:</span>
<h1>{unsignedReport.week}</h1>
<span className="ml-6 mr-2 font-bold">Signed:</span>
<h1>NO</h1>
</div>
<div className="flex">
<div className="ml-auto flex space-x-4">
<Link
to={`/PMViewUnsignedReport/${projectName}/${unsignedReport.username}/${unsignedReport.week}`}
>
<h1 className="underline cursor-pointer font-bold">
View Report
</h1>
</Link>
</div>
{unsignedReports.map((unsignedReport: WeeklyReport, index: number) => (
<h1 key={index} className="border-b-2 border-black w-full">
<div className="flex justify-between">
<div className="flex">
<span className="ml-6 mr-2 font-bold">UserID:</span>
<h1>{unsignedReport.userId}</h1>
<span className="ml-6 mr-2 font-bold">Week:</span>
<h1>{unsignedReport.week}</h1>
<span className="ml-6 mr-2 font-bold">Total Time:</span>
<h1>
{unsignedReport.developmentTime +
unsignedReport.meetingTime +
unsignedReport.adminTime +
unsignedReport.ownWorkTime +
unsignedReport.studyTime +
unsignedReport.testingTime}
</h1>
<span className="ml-6 mr-2 font-bold">Signed:</span>
<h1>NO</h1>
</div>
<div className="flex">
<div className="ml-auto flex space-x-4">
<Link
to={`/PMViewUnsignedReport/${projectName}/${unsignedReport.userId}/${unsignedReport.week}`}
>
<h1 className="underline cursor-pointer font-bold">
View Report
</h1>
</Link>
</div>
</div>
</h1>
),
)}
</div>
</h1>
))}
</div>
</>
);

View file

@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import { WeeklyReport, NewWeeklyReport } from "../Types/goTypes";
import { WeeklyReport, UpdateWeeklyReport } from "../Types/goTypes";
import { api } from "../API/API";
import { useNavigate, useParams } from "react-router-dom";
import Button from "./Button";
@ -22,6 +22,7 @@ export default function GetWeeklyReport(): JSX.Element {
projectName: string;
fetchedWeek: string;
}>();
const username = localStorage.getItem("userName") ?? "";
console.log(projectName, fetchedWeek);
useEffect(() => {
@ -60,8 +61,9 @@ export default function GetWeeklyReport(): JSX.Element {
void fetchWeeklyReport();
}, [projectName, fetchedWeek, token]);
const handleNewWeeklyReport = async (): Promise<void> => {
const newWeeklyReport: NewWeeklyReport = {
const handleUpdateWeeklyReport = async (): Promise<void> => {
const updateWeeklyReport: UpdateWeeklyReport = {
userName: username,
projectName: projectName ?? "",
week,
developmentTime,
@ -72,7 +74,7 @@ export default function GetWeeklyReport(): JSX.Element {
testingTime,
};
await api.submitWeeklyReport(newWeeklyReport, token);
await api.updateWeeklyReport(updateWeeklyReport, token);
};
const navigate = useNavigate();
@ -89,7 +91,8 @@ export default function GetWeeklyReport(): JSX.Element {
return;
}
e.preventDefault();
void handleNewWeeklyReport();
void handleUpdateWeeklyReport();
alert("Changes submitted");
navigate(-1);
}}
>
@ -128,7 +131,12 @@ export default function GetWeeklyReport(): JSX.Element {
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault();
}}
/>
@ -152,7 +160,12 @@ export default function GetWeeklyReport(): JSX.Element {
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault();
}}
/>
@ -176,7 +189,12 @@ export default function GetWeeklyReport(): JSX.Element {
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault();
}}
/>
@ -200,7 +218,12 @@ export default function GetWeeklyReport(): JSX.Element {
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault();
}}
/>
@ -224,7 +247,12 @@ export default function GetWeeklyReport(): JSX.Element {
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault();
}}
/>
@ -248,7 +276,12 @@ export default function GetWeeklyReport(): JSX.Element {
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault();
}}
/>

View file

@ -1,8 +1,8 @@
import Button from "./Button";
import DeleteUser from "./DeleteUser";
import UserProjectListAdmin from "./UserProjectListAdmin";
import { useState } from "react";
import ChangeRoleView from "./ChangeRoleView";
import RemoveUserFromProj from "./RemoveUserFromProj";
function MemberInfoModal(props: {
projectName: string;
@ -20,7 +20,7 @@ function MemberInfoModal(props: {
};
return (
<div
className="fixed inset-0 bg-black bg-opacity-30 backdrop-blur-sm
className="fixed inset-10 bg-opacity-30 backdrop-blur-sm
flex justify-center items-center"
>
<div className="border-4 border-black bg-white rounded-lg text-center flex flex-col">
@ -42,13 +42,16 @@ function MemberInfoModal(props: {
<UserProjectListAdmin username={props.username} />
<div className="items-center space-x-6">
<Button
text={"Delete"}
text={"Remove"}
onClick={function (): void {
if (
window.confirm("Are you sure you want to delete this user?")
window.confirm(
"Are you sure you want to remove this user from the project?",
)
) {
DeleteUser({
usernameToDelete: props.username,
RemoveUserFromProj({
userToRemove: props.username,
projectName: props.projectName,
});
}
}}

View file

@ -139,7 +139,12 @@ export default function NewWeeklyReport(): JSX.Element {
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault();
}}
/>
@ -163,7 +168,12 @@ export default function NewWeeklyReport(): JSX.Element {
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault();
}}
/>
@ -187,7 +197,12 @@ export default function NewWeeklyReport(): JSX.Element {
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault();
}}
/>
@ -211,7 +226,12 @@ export default function NewWeeklyReport(): JSX.Element {
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault();
}}
/>
@ -235,7 +255,12 @@ export default function NewWeeklyReport(): JSX.Element {
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault();
}}
/>
@ -259,7 +284,12 @@ export default function NewWeeklyReport(): JSX.Element {
}}
onKeyDown={(event) => {
const keyValue = event.key;
if (!/\d/.test(keyValue) && keyValue !== "Backspace")
if (
!/\d/.test(keyValue) &&
keyValue !== "Backspace" &&
keyValue !== "ArrowLeft" &&
keyValue !== "ArrowRight"
)
event.preventDefault();
}}
/>

View file

@ -29,6 +29,7 @@ export default function OtherUsersTR(): JSX.Element {
projectName ?? "",
fetchedWeek?.toString() ?? "0",
token,
username ?? "",
);
if (response.success) {
@ -86,6 +87,7 @@ export default function OtherUsersTR(): JSX.Element {
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={developmentTime === 0 ? "" : developmentTime}
readOnly
/>
</td>
</tr>
@ -97,6 +99,7 @@ export default function OtherUsersTR(): JSX.Element {
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={meetingTime === 0 ? "" : meetingTime}
readOnly
/>
</td>
</tr>
@ -108,6 +111,7 @@ export default function OtherUsersTR(): JSX.Element {
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={adminTime === 0 ? "" : adminTime}
readOnly
/>
</td>
</tr>
@ -119,6 +123,7 @@ export default function OtherUsersTR(): JSX.Element {
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={ownWorkTime === 0 ? "" : ownWorkTime}
readOnly
/>
</td>
</tr>
@ -130,6 +135,7 @@ export default function OtherUsersTR(): JSX.Element {
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={studyTime === 0 ? "" : studyTime}
readOnly
/>
</td>
</tr>
@ -141,6 +147,7 @@ export default function OtherUsersTR(): JSX.Element {
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={testingTime === 0 ? "" : testingTime}
readOnly
/>
</td>
</tr>

View file

@ -15,17 +15,21 @@ export default function Register(): JSX.Element {
const [errMessage, setErrMessage] = useState<string>();
const handleRegister = async (): Promise<void> => {
if (username === "" || password === "") {
alert("Must provide username and password");
return;
}
const newUser: NewUser = {
username: username ?? "",
username: username?.replace(/ /g, "") ?? "",
password: password ?? "",
};
const response = await api.registerUser(newUser);
if (response.success) {
alert("User added!");
alert(`${newUser.username} added!`);
setPassword("");
setUsername("");
} else {
alert("User not added");
alert("User not added, name could be taken");
setErrMessage(response.message ?? "Unknown error");
console.error(errMessage);
}

View file

@ -0,0 +1,41 @@
import { api, APIResponse } from "../API/API";
/**
* Removes a user from a project
* @param {string} props.usernameToDelete - The username of user to remove
* @param {string} props.projectName - Project to remove user from
* @returns {void}
* @example
* const exampleUsername = "user";
* const exampleProjectName "project";
* RemoveUserFromProj({ userToRemove: exampleUsername, projectName: exampleProjectName });
*/
export default function RemoveUserFromProj(props: {
userToRemove: string;
projectName: string;
}): void {
if (props.userToRemove === localStorage.getItem("username")) {
alert("Cannot remove yourself");
return;
}
api
.removeUserFromProject(
props.userToRemove,
props.projectName,
localStorage.getItem("accessToken") ?? "",
)
.then((response: APIResponse<void>) => {
if (response.success) {
alert(`${props.userToRemove} has been removed!`);
location.reload();
} else {
alert(`${props.userToRemove} has not been removed due to an error`);
console.error(response.message);
}
})
.catch((error) => {
alert(`${props.userToRemove} has not been removed due to an error`);
console.error("An error occurred during deletion:", error);
});
}

View file

@ -1,70 +1,45 @@
import { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import { api } from "../API/API";
import { projectTimes } from "./GetProjectTimes";
/**
* Renders the component for showing total time per role in a project.
* @returns JSX.Element
*/
export default function TimePerRole(): JSX.Element {
const [developmentTime, setDevelopmentTime] = useState<number>();
const [meetingTime, setMeetingTime] = useState<number>();
const [adminTime, setAdminTime] = useState<number>();
const [ownWorkTime, setOwnWorkTime] = useState<number>();
const [studyTime, setStudyTime] = useState<number>();
const [testingTime, setTestingTime] = useState<number>();
const [development, setDevelopment] = useState<number>(0);
const [meeting, setMeeting] = useState<number>(0);
const [admin, setAdmin] = useState<number>(0);
const [own_work, setOwnWork] = useState<number>(0);
const [study, setStudy] = useState<number>(0);
const [testing, setTesting] = useState<number>(0);
// const token = localStorage.getItem("accessToken") ?? "";
// const username = localStorage.getItem("username") ?? "";
const token = localStorage.getItem("accessToken") ?? "";
const { projectName } = useParams();
// const fetchTimePerRole = async (): Promise<void> => {
// const response = await api.getTimePerRole(
// username,
// projectName ?? "",
// token,
// );
// {
// if (response.success) {
// const report: TimePerRole = response.data ?? {
// PManagerTime: 0,
// SManagerTime: 0,
// DeveloperTime: 0,
// TesterTime: 0,
// };
// } else {
// console.error("Failed to fetch weekly report:", response.message);
// }
// }
interface TimePerActivity {
developmentTime: number;
meetingTime: number;
adminTime: number;
ownWorkTime: number;
studyTime: number;
testingTime: number;
}
const fetchTimePerActivity = async (): Promise<void> => {
// Use mock data
const report: TimePerActivity = {
developmentTime: 100,
meetingTime: 200,
adminTime: 300,
ownWorkTime: 50,
studyTime: 75,
testingTime: 110,
};
// Set the state with the mock data
setDevelopmentTime(report.developmentTime);
setMeetingTime(report.meetingTime);
setAdminTime(report.adminTime);
setOwnWorkTime(report.ownWorkTime);
setStudyTime(report.studyTime);
setTestingTime(report.testingTime);
await Promise.resolve();
const response = await api.getProjectTimes(projectName ?? "", token);
{
if (response.success) {
const report: projectTimes = response.data ?? {
development: 0,
meeting: 0,
admin: 0,
own_work: 0,
study: 0,
testing: 0,
};
setDevelopment(report.development);
setMeeting(report.meeting);
setAdmin(report.admin);
setOwnWork(report.own_work);
setStudy(report.study);
setTesting(report.testing);
} else {
console.error("Failed to fetch weekly report:", response.message);
}
}
};
useEffect(() => {
@ -94,10 +69,8 @@ export default function TimePerRole(): JSX.Element {
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={developmentTime}
onKeyDown={(event) => {
event.preventDefault();
}}
value={development}
readOnly
/>
</td>
</tr>
@ -107,10 +80,8 @@ export default function TimePerRole(): JSX.Element {
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={meetingTime}
onKeyDown={(event) => {
event.preventDefault();
}}
value={meeting}
readOnly
/>
</td>
</tr>
@ -120,10 +91,8 @@ export default function TimePerRole(): JSX.Element {
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={adminTime}
onKeyDown={(event) => {
event.preventDefault();
}}
value={admin}
readOnly
/>
</td>
</tr>
@ -133,10 +102,8 @@ export default function TimePerRole(): JSX.Element {
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={ownWorkTime}
onKeyDown={(event) => {
event.preventDefault();
}}
value={own_work}
readOnly
/>
</td>
</tr>
@ -146,10 +113,8 @@ export default function TimePerRole(): JSX.Element {
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={studyTime}
onKeyDown={(event) => {
event.preventDefault();
}}
value={study}
readOnly
/>
</td>
</tr>
@ -159,10 +124,8 @@ export default function TimePerRole(): JSX.Element {
<input
type="string"
className="border-2 border-black rounded-md text-center w-1/2"
value={testingTime}
onKeyDown={(event) => {
event.preventDefault();
}}
value={testing}
readOnly
/>
</td>
</tr>

View file

@ -28,7 +28,7 @@ function UserInfoModal(props: {
const handleClickChangeName = (): void => {
const nameChange: StrNameChange = {
prevName: props.username,
newName: newUsername,
newName: newUsername.replace(/ /g, ""),
};
ChangeUsername({ nameChange: nameChange });
};

View file

@ -1,5 +1,5 @@
import { useState, useEffect } from "react";
import { WeeklyReport, NewWeeklyReport } from "../Types/goTypes";
import { WeeklyReport } from "../Types/goTypes";
import { api } from "../API/API";
import { useNavigate, useParams } from "react-router-dom";
import Button from "./Button";
@ -18,6 +18,7 @@ export default function GetOtherUsersReport(): JSX.Element {
const [ownWorkTime, setOwnWorkTime] = useState(0);
const [studyTime, setStudyTime] = useState(0);
const [testingTime, setTestingTime] = useState(0);
const [reportId, setReportId] = useState(0);
const token = localStorage.getItem("accessToken") ?? "";
const { projectName } = useParams();
@ -45,6 +46,7 @@ export default function GetOtherUsersReport(): JSX.Element {
studyTime: 0,
testingTime: 0,
};
setReportId(report.reportId);
setWeek(report.week);
setDevelopmentTime(report.developmentTime);
setMeetingTime(report.meetingTime);
@ -61,30 +63,23 @@ export default function GetOtherUsersReport(): JSX.Element {
});
const handleSignWeeklyReport = async (): Promise<void> => {
const newWeeklyReport: NewWeeklyReport = {
projectName: projectName ?? "",
week,
developmentTime,
meetingTime,
adminTime,
ownWorkTime,
studyTime,
testingTime,
};
await api.submitWeeklyReport(newWeeklyReport, token);
await api.signReport(reportId, token);
};
const navigate = useNavigate();
return (
<>
<h1 className="text-[30px] font-bold">{username}&apos;s Report</h1>
<h1 className="text-[30px] font-bold">
{" "}
UserId: {username}&apos;s Report
</h1>
<div className="border-4 border-black bg-white flex flex-col justify-start min-h-[65vh] h-fit w-[50vw] rounded-3xl overflow-scroll space-y-[2vh] p-[30px] items-center">
<form
onSubmit={(e) => {
e.preventDefault();
void handleSignWeeklyReport();
alert("Report successfully signed!");
navigate(-1);
}}
>
@ -112,7 +107,10 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={developmentTime === 0 ? "" : developmentTime}
defaultValue={
developmentTime === 0 ? "" : developmentTime
}
readOnly
/>
</td>
</tr>
@ -123,7 +121,8 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={meetingTime === 0 ? "" : meetingTime}
defaultValue={meetingTime === 0 ? "" : meetingTime}
readOnly
/>
</td>
</tr>
@ -134,7 +133,8 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={adminTime === 0 ? "" : adminTime}
defaultValue={adminTime === 0 ? "" : adminTime}
readOnly
/>
</td>
</tr>
@ -145,7 +145,8 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={ownWorkTime === 0 ? "" : ownWorkTime}
defaultValue={ownWorkTime === 0 ? "" : ownWorkTime}
readOnly
/>
</td>
</tr>
@ -156,7 +157,8 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={studyTime === 0 ? "" : studyTime}
defaultValue={studyTime === 0 ? "" : studyTime}
readOnly
/>
</td>
</tr>
@ -167,7 +169,8 @@ export default function GetOtherUsersReport(): JSX.Element {
type="text"
min="0"
className="border-2 border-black rounded-md text-center w-1/2"
value={testingTime === 0 ? "" : testingTime}
defaultValue={testingTime === 0 ? "" : testingTime}
readOnly
/>
</td>
</tr>

151
testing/helpers.py Normal file
View file

@ -0,0 +1,151 @@
import requests
import string
import random
import json
# Helper function for the TTime API testing suite
# For style guide, see:
# https://peps.python.org/pep-0008/#function-and-variable-names
# https://google.github.io/styleguide/pyguide.html#316-naming
##################
## Static Paths ##
##################
base_url = "http://localhost:8080"
registerPath = base_url + "/api/register"
loginPath = base_url + "/api/login"
addProjectPath = base_url + "/api/project"
submitReportPath = base_url + "/api/submitWeeklyReport"
getWeeklyReportPath = base_url + "/api/getWeeklyReport"
getProjectPath = base_url + "/api/project"
signReportPath = base_url + "/api/signReport"
addUserToProjectPath = base_url + "/api/addUserToProject"
promoteToAdminPath = base_url + "/api/promoteToAdmin"
getUserProjectsPath = base_url + "/api/getUserProjects"
getAllWeeklyReportsPath = base_url + "/api/getAllWeeklyReports"
checkIfProjectManagerPath = base_url + "/api/checkIfProjectManager"
ProjectRoleChangePath = base_url + "/api/ProjectRoleChange"
getUsersProjectPath = base_url + "/api/getUsersProject"
getUnsignedReportsPath = base_url + "/api/getUnsignedReports"
getChangeUserNamePath = base_url + "/api/changeUserName"
getUpdateWeeklyReportPath = base_url + "/api/updateWeeklyReport"
removeProjectPath = base_url + "/api/removeProject"
promoteToPmPath = base_url + "/api/promoteToPm"
debug_output = True
def gprint(*args, **kwargs):
print("\033[92m", *args, "\033[00m", **kwargs)
def dprint(*args, **kwargs):
if debug_output:
print(*args, **kwargs)
def randomString(len=10):
"""Generate a random string of fixed length"""
letters = string.ascii_lowercase
return "".join(random.choice(letters) for i in range(len))
############ ############ ############ ############ ############
# Posts the username and password to the register endpoint
def register(username: string, password: string):
dprint("Registering with username: ", username, " and password: ", password)
response = requests.post(
registerPath, json={"username": username, "password": password}
)
dprint(response.text)
return response
# Posts the username and password to the login endpoint
def login(username: string, password: string):
dprint("Logging in with username: ", username, " and password: ", password)
response = requests.post(
loginPath, json={"username": username, "password": password}
)
dprint(response.text)
return response
# Register a user and return the token
def register_and_login(username: string, password: string) -> string:
register(username, password)
response = login(username, password)
return response.json()["token"]
def create_project(
token: string, project_name: string, description: string = "Test description"
):
dprint("Creating project with name: ", project_name)
response = requests.post(
addProjectPath,
headers={"Authorization": "Bearer " + token},
json={"name": project_name, "description": description},
)
dprint(response.text)
return response
# Add a user to a project, requires the user withing the token to be a project manager of said project
def addToProject(token: string, username: string, project_name: string):
dprint("Adding user with username: ", username, " to project: ", project_name)
response = requests.put(
addUserToProjectPath + "/" + project_name,
headers={"Authorization": "Bearer " + token},
params={"userName": username},
)
dprint(response.text)
return response
def promoteToManager(token: string, username: string, project_name: string):
dprint(
"Promoting user with username: ",
username,
" to project manager of project: ",
project_name,
)
response = requests.put(
promoteToPmPath + "/" + project_name,
headers={"Authorization": "Bearer " + token},
params={"userName": username},
)
dprint(response.text)
return response
def submitReport(token: string, report):
dprint("Submitting report: ", report)
response = requests.post(
submitReportPath,
json=report,
headers={"Authorization": "Bearer " + token},
)
return response
def getReport(token: string, username: string, projectName: string):
# Retrieve the report ID
response = requests.get(
getWeeklyReportPath,
headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName, "week": 1},
)
return response.json()
def signReport(project_manager_token: string, report_id: int):
return requests.put(
signReportPath + "/" + str(report_id),
headers={"Authorization": "Bearer " + project_manager_token},
)

View file

@ -1,61 +1,23 @@
import requests
import string
import random
debug_output = True
def gprint(*args, **kwargs):
print("\033[92m", *args, "\033[00m", **kwargs)
# This modules contains helper functions for the tests
from helpers import *
print("Running Tests...")
def dprint(*args, **kwargs):
if debug_output:
print(*args, **kwargs)
def randomString(len=10):
"""Generate a random string of fixed length"""
letters = string.ascii_lowercase
return "".join(random.choice(letters) for i in range(len))
# Defined once per test run
username = "user_" + randomString()
projectName = "project_" + randomString()
# The base URL of the API
base_url = "http://localhost:8080"
# Endpoint to test
registerPath = base_url + "/api/register"
loginPath = base_url + "/api/login"
addProjectPath = base_url + "/api/project"
submitReportPath = base_url + "/api/submitWeeklyReport"
getWeeklyReportPath = base_url + "/api/getWeeklyReport"
getProjectPath = base_url + "/api/project"
signReportPath = base_url + "/api/signReport"
addUserToProjectPath = base_url + "/api/addUserToProject"
promoteToAdminPath = base_url + "/api/promoteToAdmin"
getUserProjectsPath = base_url + "/api/getUserProjects"
getWeeklyReportsUserPath = base_url + "/api/getWeeklyReportsUser"
checkIfProjectManagerPath = base_url + "/api/checkIfProjectManager"
ProjectRoleChangePath = base_url + "/api/ProjectRoleChange"
getUsersProjectPath = base_url + "/api/getUsersProject"
getUnsignedReportsPath = base_url + "/api/getUnsignedReports"
getChangeUserNamePath = base_url + "/api/changeUserName"
getUpdateWeeklyReportPath = base_url + "/api/updateWeeklyReport"
removeProjectPath = base_url + "/api/removeProject"
#ta bort auth i handlern för att få testet att gå igenom
# ta bort auth i handlern för att få testet att gå igenom
def test_ProjectRoleChange():
dprint("Testing ProjectRoleChange")
localUsername = randomString()
localProjectName = randomString()
register(localUsername, "username_password")
token = login(localUsername, "username_password").json()[
"token"
]
token = login(localUsername, "username_password").json()["token"]
# Just checking since this test is built somewhat differently than the others
assert token != None, "Login failed"
@ -80,16 +42,17 @@ def test_ProjectRoleChange():
assert response.status_code == 200, "ProjectRoleChange failed"
gprint("test_ProjectRoleChange successful")
def test_get_user_projects():
username = "user2"
password = "123"
dprint("Testing get user projects")
loginResponse = login("user2", "123")
loginResponse = login(username, password)
# Check if the user is added to the project
response = requests.get(
getUserProjectsPath,
json={"username": "user2"},
getUserProjectsPath + "/" + username,
headers={"Authorization": "Bearer " + loginResponse.json()["token"]},
)
dprint(response.text)
@ -98,26 +61,6 @@ def test_get_user_projects():
gprint("test_get_user_projects successful")
# Posts the username and password to the register endpoint
def register(username: string, password: string):
dprint("Registering with username: ", username, " and password: ", password)
response = requests.post(
registerPath, json={"username": username, "password": password}
)
dprint(response.text)
return response
# Posts the username and password to the login endpoint
def login(username: string, password: string):
dprint("Logging in with username: ", username, " and password: ", password)
response = requests.post(
loginPath, json={"username": username, "password": password}
)
dprint(response.text)
return response
# Test function to login
def test_login():
response = login(username, "always_same")
@ -133,6 +76,7 @@ def test_create_user():
assert response.status_code == 200, "Registration failed"
gprint("test_create_user successful")
# Test function to add a project
def test_add_project():
loginResponse = login(username, "always_same")
@ -146,6 +90,7 @@ def test_add_project():
assert response.status_code == 200, "Add project failed"
gprint("test_add_project successful")
# Test function to submit a report
def test_submit_report():
token = login(username, "always_same").json()["token"]
@ -167,6 +112,7 @@ def test_submit_report():
assert response.status_code == 200, "Submit report failed"
gprint("test_submit_report successful")
# Test function to get a weekly report
def test_get_weekly_report():
token = login(username, "always_same").json()["token"]
@ -194,91 +140,58 @@ def test_get_project():
# Test function to add a user to a project
def test_add_user_to_project():
# Log in as a site admin
admin_username = randomString()
admin_password = "admin_password"
dprint(
"Registering with username: ", admin_username, " and password: ", admin_password
)
response = requests.post(
registerPath, json={"username": admin_username, "password": admin_password}
)
dprint(response.text)
# User to create
pm_user = "user" + randomString()
pm_passwd = "password"
admin_token = login(admin_username, admin_password).json()["token"]
response = requests.post(
promoteToAdminPath,
json={"username": admin_username},
headers={"Authorization": "Bearer " + admin_token},
)
dprint(response.text)
assert response.status_code == 200, "Promote to site admin failed"
dprint("Admin promoted to site admin successfully")
# User to add
member_user = "member" + randomString()
member_passwd = "password"
# Create a new user to add to the project
new_user = randomString()
register(new_user, "new_user_password")
# Name of the project to be created
project_name = "project" + randomString()
# Add the new user to the project as a member
response = requests.put(
addUserToProjectPath,
json={"projectName": projectName, "username": new_user, "role": "member"},
headers={"Authorization": "Bearer " + admin_token},
)
pm_token = register_and_login(pm_user, pm_passwd)
register(member_user, member_passwd)
dprint(response.text)
response = create_project(pm_token, project_name)
assert response.status_code == 200, "Create project failed"
# Promote the user to project manager
response = addToProject(pm_token, member_user, project_name)
assert response.status_code == 200, "Add user to project failed"
gprint("test_add_user_to_project successful")
# Test function to sign a report
def test_sign_report():
# Create a project manager user
project_manager = randomString()
register(project_manager, "project_manager_password")
# Pm user
pm_username = "pm" + randomString()
pm_password = "admin_password2"
# Register an admin
admin_username = randomString()
admin_password = "admin_password2"
dprint(
"Registering with username: ", admin_username, " and password: ", admin_password
)
response = requests.post(
registerPath, json={"username": admin_username, "password": admin_password}
)
dprint(response.text)
# User to add
member_user = "member" + randomString()
member_passwd = "password"
# Log in as the admin
admin_token = login(admin_username, admin_password).json()["token"]
response = requests.post(
promoteToAdminPath,
json={"username": admin_username},
headers={"Authorization": "Bearer " + admin_token},
)
# Name of the project to be created
project_name = "project" + randomString()
response = requests.put(
addUserToProjectPath,
json={
"projectName": projectName,
"username": project_manager,
"role": "project_manager",
},
headers={"Authorization": "Bearer " + admin_token},
)
assert response.status_code == 200, "Add project manager to project failed"
dprint("Project manager added to project successfully")
# Register and get the tokens for both users
pm_token = register_and_login(pm_username, pm_password)
member_token = register_and_login(member_user, member_passwd)
# Log in as the project manager
project_manager_token = login(project_manager, "project_manager_password").json()[
"token"
]
# Create the project
response = create_project(pm_token, project_name)
assert response.status_code == 200, "Create project failed"
# Add the user to the project
response = addToProject(pm_token, member_user, project_name)
# Submit a report for the project
token = login(username, "always_same").json()["token"]
response = requests.post(
submitReportPath,
json={
"projectName": projectName,
"week": 2,
response = submitReport(
member_token,
{
"projectName": project_name,
"week": 1,
"developmentTime": 10,
"meetingTime": 5,
"adminTime": 5,
@ -286,54 +199,40 @@ def test_sign_report():
"studyTime": 10,
"testingTime": 10,
},
headers={"Authorization": "Bearer " + token},
)
assert response.status_code == 200, "Submit report failed"
dprint("Submit report successful")
# Retrieve the report ID
response = requests.get(
getWeeklyReportPath,
headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName, "week": 1},
)
dprint(response.text)
report_id = response.json()["reportId"]
report_id = getReport(member_token, member_user, project_name)["reportId"]
# Sign the report as the project manager
response = requests.put(
signReportPath + "/" + str(report_id),
headers={"Authorization": "Bearer " + project_manager_token},
)
response = signReport(pm_token, report_id)
assert response.status_code == 200, "Sign report failed"
dprint("Sign report successful")
# Retrieve the report ID again for confirmation
response = requests.get(
getWeeklyReportPath,
headers={"Authorization": "Bearer " + token},
params={"username": username, "projectName": projectName, "week": 1},
)
dprint(response.text)
report_id = getReport(member_token, member_user, project_name)["reportId"]
assert report_id != None, "Get report failed"
gprint("test_sign_report successful")
# Test function to get weekly reports for a user in a project
def test_get_weekly_reports_user():
def test_get_all_weekly_reports():
# Log in as the user
token = login(username, "always_same").json()["token"]
# Get weekly reports for the user in the project
response = requests.get(
getWeeklyReportsUserPath + "/" + projectName,
getAllWeeklyReportsPath + "/" + projectName,
headers={"Authorization": "Bearer " + token},
params={"targetUser": username},
)
dprint(response.text)
assert response.status_code == 200, "Get weekly reports for user failed"
gprint("test_get_weekly_reports_user successful")
# Test function to check if a user is a project manager
def test_check_if_project_manager():
# Log in as the user
@ -344,11 +243,12 @@ def test_check_if_project_manager():
checkIfProjectManagerPath + "/" + projectName,
headers={"Authorization": "Bearer " + token},
)
dprint(response.text)
assert response.status_code == 200, "Check if project manager failed"
gprint("test_check_if_project_manager successful")
def test_ensure_manager_of_created_project():
# Create a new user to add to the project
newUser = "karen_" + randomString()
@ -372,6 +272,7 @@ def test_ensure_manager_of_created_project():
assert response.json()["isProjectManager"] == True, "User is not project manager"
gprint("test_ensure_admin_of_created_project successful")
def test_change_user_name():
# Register a new user
new_user = randomString()
@ -391,7 +292,7 @@ def test_change_user_name():
)
admin_token = login(admin_username, admin_password).json()["token"]
# Promote to admin
# Promote to admin
response = requests.post(
promoteToAdminPath,
json={"username": admin_username},
@ -409,6 +310,7 @@ def test_change_user_name():
assert response.status_code == 200, "Change user name failed"
gprint("test_change_user_name successful")
def test_list_all_users_project():
# Log in as a user who is a member of the project
admin_username = randomString()
@ -432,11 +334,12 @@ def test_list_all_users_project():
# Make a request to list all users associated with the project
response = requests.get(
getUsersProjectPath + "/" + projectName,
headers={"Authorization": "Bearer " + admin_token},
headers={"Authorization": "Bearer " + admin_token},
)
assert response.status_code == 200, "List all users project failed"
gprint("test_list_all_users_project sucessful")
def test_update_weekly_report():
# Log in as the user
token = login(username, "always_same").json()["token"]
@ -502,20 +405,99 @@ def test_remove_project():
assert response.status_code == 200, "Remove project failed"
gprint("test_remove_project successful")
def test_get_unsigned_reports():
# Log in as the user
token = login("user2", "123").json()["token"]
token = login("user2", "123").json()["token"]
# Make a request to get all unsigned reports
response = requests.get(
getUnsignedReportsPath + "/" + projectName,
headers={"Authorization": "Bearer " + token},
)
assert response.status_code == 200, "Get unsigned reports failed"
gprint("test_get_unsigned_reports successful")
# Make a request to get all unsigned reports
response = requests.get(
getUnsignedReportsPath + "/" + projectName,
headers={"Authorization": "Bearer " + token},
)
assert response.status_code == 200, "Get unsigned reports failed"
gprint("test_get_unsigned_reports successful")
def test_get_other_users_report_as_pm():
# Create user
user = randomString()
register(user, "password")
# Create project
project = randomString()
pm_token = login(user, "password").json()["token"]
response = requests.post(
addProjectPath,
json={"name": project, "description": "This is a project"},
headers={"Authorization": "Bearer " + pm_token},
)
assert response.status_code == 200, "Add project failed"
# Create other user
other_user = randomString()
register(other_user, "password")
user_token = login(other_user, "password").json()["token"]
# Add other user to project
response = requests.put(
addUserToProjectPath + "/" + project,
headers={"Authorization": "Bearer " + pm_token}, # note pm_token
params={"userName": other_user},
)
assert response.status_code == 200, "Add user to project failed"
# Submit report as other user
response = requests.post(
submitReportPath,
json={
"projectName": project,
"week": 1,
"developmentTime": 10,
"meetingTime": 5,
"adminTime": 5,
"ownWorkTime": 10,
"studyTime": 10,
"testingTime": 10,
},
headers={"Authorization": "Bearer " + user_token},
)
assert response.status_code == 200, "Submit report failed"
# Get report as project manager
response = requests.get(
getWeeklyReportPath,
headers={"Authorization": "Bearer " + pm_token},
params={"targetUser": other_user, "projectName": project, "week": 1},
)
assert response.status_code == 200, "Get weekly report failed"
def test_promote_to_manager():
# User to create
pm_user = "user" + randomString()
pm_passwd = "password"
# User to promote
member_user = "member" + randomString()
member_passwd = "password"
# Name of the project to be created
project_name = "project" + randomString()
pm_token = register_and_login(pm_user, pm_passwd)
member_token = register_and_login(member_user, member_passwd)
response = create_project(pm_token, project_name)
assert response.status_code == 200, "Create project failed"
# Promote the user to project manager
response = promoteToManager(pm_token, member_user, project_name)
assert response.status_code == 200, "Promote to manager failed"
if __name__ == "__main__":
test_promote_to_manager()
test_remove_project()
test_get_user_projects()
test_create_user()
@ -526,7 +508,7 @@ if __name__ == "__main__":
test_get_project()
test_sign_report()
test_add_user_to_project()
test_get_weekly_reports_user()
test_get_all_weekly_reports()
test_check_if_project_manager()
test_ProjectRoleChange()
test_ensure_manager_of_created_project()
@ -534,4 +516,4 @@ if __name__ == "__main__":
test_list_all_users_project()
test_change_user_name()
test_update_weekly_report()
test_get_other_users_report_as_pm()