getStatistics handler, db-interface and ts API
This commit is contained in:
parent
2d2b63938c
commit
fe9d5f74bb
8 changed files with 188 additions and 1 deletions
|
@ -2,11 +2,13 @@ package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"embed"
|
"embed"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"ttime/internal/types"
|
"ttime/internal/types"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2/log"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite"
|
||||||
)
|
)
|
||||||
|
@ -41,6 +43,7 @@ type Database interface {
|
||||||
SignWeeklyReport(reportId int, projectManagerId int) error
|
SignWeeklyReport(reportId int, projectManagerId int) error
|
||||||
IsSiteAdmin(username string) (bool, error)
|
IsSiteAdmin(username string) (bool, error)
|
||||||
IsProjectManager(username string, projectname string) (bool, error)
|
IsProjectManager(username string, projectname string) (bool, error)
|
||||||
|
ReportStatistics(username string, projectName string) (*types.Statistics, error)
|
||||||
GetProjectTimes(projectName string) (map[string]int, error)
|
GetProjectTimes(projectName string) (map[string]int, error)
|
||||||
UpdateWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error
|
UpdateWeeklyReport(projectName string, userName string, week int, developmentTime int, meetingTime int, adminTime int, ownWorkTime int, studyTime int, testingTime int) error
|
||||||
RemoveProject(projectname string) error
|
RemoveProject(projectname string) error
|
||||||
|
@ -94,6 +97,17 @@ const removeUserFromProjectQuery = `DELETE FROM user_roles
|
||||||
WHERE user_id = (SELECT id FROM users WHERE username = ?)
|
WHERE user_id = (SELECT id FROM users WHERE username = ?)
|
||||||
AND project_id = (SELECT id FROM projects WHERE name = ?)`
|
AND project_id = (SELECT id FROM projects WHERE name = ?)`
|
||||||
|
|
||||||
|
const reportStatistics = `SELECT SUM(development_time) AS total_development_time,
|
||||||
|
SUM(meeting_time) AS total_meeting_time,
|
||||||
|
SUM(admin_time) AS total_admin_time,
|
||||||
|
SUM(own_work_time) AS total_own_work_time,
|
||||||
|
SUM(study_time) AS total_study_time,
|
||||||
|
SUM(testing_time) AS total_testing_time
|
||||||
|
FROM weekly_reports
|
||||||
|
WHERE user_id = (SELECT id FROM users WHERE username = ?)
|
||||||
|
AND project_id = (SELECT id FROM projects WHERE name = ?)
|
||||||
|
GROUP BY user_id, project_id`
|
||||||
|
|
||||||
// DbConnect connects to the database
|
// DbConnect connects to the database
|
||||||
func DbConnect(dbpath string) Database {
|
func DbConnect(dbpath string) Database {
|
||||||
// Open the database
|
// Open the database
|
||||||
|
@ -111,6 +125,24 @@ func DbConnect(dbpath string) Database {
|
||||||
return &Db{db}
|
return &Db{db}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Db) ReportStatistics(username string, projectName string) (*types.Statistics, error) {
|
||||||
|
var result types.Statistics
|
||||||
|
|
||||||
|
err := d.Get(&result, reportStatistics, username, projectName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
serialized, err := json.Marshal(result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info(string(serialized))
|
||||||
|
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Db) CheckUser(username string, password string) bool {
|
func (d *Db) CheckUser(username string, password string) bool {
|
||||||
var dbPassword string
|
var dbPassword string
|
||||||
err := d.Get(&dbPassword, "SELECT password FROM users WHERE username = ?", username)
|
err := d.Get(&dbPassword, "SELECT password FROM users WHERE username = ?", username)
|
||||||
|
|
50
backend/internal/handlers/reports/Statistics.go
Normal file
50
backend/internal/handlers/reports/Statistics.go
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
package reports
|
||||||
|
|
||||||
|
import (
|
||||||
|
db "ttime/internal/database"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v2"
|
||||||
|
"github.com/gofiber/fiber/v2/log"
|
||||||
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetStatistics(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 from query parameters
|
||||||
|
projectName := c.Query("projectName")
|
||||||
|
|
||||||
|
log.Info(username, " trying to get statistics for project: ", projectName)
|
||||||
|
|
||||||
|
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't view statistics
|
||||||
|
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 {
|
||||||
|
log.Info("Unauthorized access")
|
||||||
|
return c.Status(403).SendString("Unauthorized access")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve statistics for the project from the database
|
||||||
|
statistics, err := db.GetDb(c).ReportStatistics(username, projectName)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Error getting statistics for project:", projectName, ":", err)
|
||||||
|
return c.Status(500).SendString(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Returning statistics")
|
||||||
|
// Return the retrieved statistics
|
||||||
|
return c.JSON(statistics)
|
||||||
|
|
||||||
|
}
|
|
@ -66,6 +66,15 @@ type WeeklyReport struct {
|
||||||
SignedBy *int `json:"signedBy" db:"signed_by"`
|
SignedBy *int `json:"signedBy" db:"signed_by"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Statistics struct {
|
||||||
|
TotalDevelopmentTime int `json:"totalDevelopmentTime" db:"total_development_time"`
|
||||||
|
TotalMeetingTime int `json:"totalMeetingTime" db:"total_meeting_time"`
|
||||||
|
TotalAdminTime int `json:"totalAdminTime" db:"total_admin_time"`
|
||||||
|
TotalOwnWorkTime int `json:"totalOwnWorkTime" db:"total_own_work_time"`
|
||||||
|
TotalStudyTime int `json:"totalStudyTime" db:"total_study_time"`
|
||||||
|
TotalTestingTime int `json:"totalTestingTime" db:"total_testing_time"`
|
||||||
|
}
|
||||||
|
|
||||||
type UpdateWeeklyReport struct {
|
type UpdateWeeklyReport struct {
|
||||||
// The name of the project, as it appears in the database
|
// The name of the project, as it appears in the database
|
||||||
ProjectName string `json:"projectName"`
|
ProjectName string `json:"projectName"`
|
||||||
|
|
|
@ -126,12 +126,12 @@ func main() {
|
||||||
api.Delete("/removeProject/:projectName", projects.RemoveProject)
|
api.Delete("/removeProject/:projectName", projects.RemoveProject)
|
||||||
api.Delete("/project/:projectID", projects.DeleteProject)
|
api.Delete("/project/:projectID", projects.DeleteProject)
|
||||||
|
|
||||||
|
|
||||||
// All report related routes
|
// All report related routes
|
||||||
// reportGroup := api.Group("/report") // Not currently in use
|
// reportGroup := api.Group("/report") // Not currently in use
|
||||||
api.Get("/getWeeklyReport", reports.GetWeeklyReport)
|
api.Get("/getWeeklyReport", reports.GetWeeklyReport)
|
||||||
api.Get("/getUnsignedReports/:projectName", reports.GetUnsignedReports)
|
api.Get("/getUnsignedReports/:projectName", reports.GetUnsignedReports)
|
||||||
api.Get("/getAllWeeklyReports/:projectName", reports.GetAllWeeklyReports)
|
api.Get("/getAllWeeklyReports/:projectName", reports.GetAllWeeklyReports)
|
||||||
|
api.Get("/getStatistics", reports.GetStatistics)
|
||||||
api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport)
|
api.Post("/submitWeeklyReport", reports.SubmitWeeklyReport)
|
||||||
api.Put("/signReport/:reportId", reports.SignReport)
|
api.Put("/signReport/:reportId", reports.SignReport)
|
||||||
api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport)
|
api.Put("/updateWeeklyReport", reports.UpdateWeeklyReport)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
NewProject,
|
NewProject,
|
||||||
WeeklyReport,
|
WeeklyReport,
|
||||||
StrNameChange,
|
StrNameChange,
|
||||||
|
Statistics,
|
||||||
} from "../Types/goTypes";
|
} from "../Types/goTypes";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -258,6 +259,17 @@ interface API {
|
||||||
reportId: number,
|
reportId: number,
|
||||||
token: string,
|
token: string,
|
||||||
): Promise<APIResponse<string>>;
|
): Promise<APIResponse<string>>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the total time spent on a project for a particular user (the user is determined by the token)
|
||||||
|
*
|
||||||
|
* @param {string} projectName The name of the project
|
||||||
|
* @param {string} token The authentication token
|
||||||
|
*/
|
||||||
|
getStatistics(
|
||||||
|
projectName: string,
|
||||||
|
token: string,
|
||||||
|
): Promise<APIResponse<Statistics>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An instance of the API */
|
/** An instance of the API */
|
||||||
|
@ -962,4 +974,30 @@ export const api: API = {
|
||||||
return { success: false, message: "Failed to delete report" };
|
return { success: false, message: "Failed to delete report" };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async getStatistics(
|
||||||
|
token: string,
|
||||||
|
projectName: string,
|
||||||
|
): Promise<APIResponse<Statistics>> {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`/api/getStatistics/?projectName=${projectName}`,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: "Bearer " + token,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
return { success: false, message: "Failed to get statistics" };
|
||||||
|
} else {
|
||||||
|
const data = (await response.json()) as Statistics;
|
||||||
|
return { success: true, data };
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
return { success: false, message: "Failed to get statistics" };
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -124,6 +124,14 @@ export interface WeeklyReport {
|
||||||
*/
|
*/
|
||||||
signedBy?: number /* int */;
|
signedBy?: number /* int */;
|
||||||
}
|
}
|
||||||
|
export interface Statistics {
|
||||||
|
totalDevelopmentTime: number /* int */;
|
||||||
|
totalMeetingTime: number /* int */;
|
||||||
|
totalAdminTime: number /* int */;
|
||||||
|
totalOwnWorkTime: number /* int */;
|
||||||
|
totalStudyTime: number /* int */;
|
||||||
|
totalTestingTime: number /* int */;
|
||||||
|
}
|
||||||
export interface UpdateWeeklyReport {
|
export interface UpdateWeeklyReport {
|
||||||
/**
|
/**
|
||||||
* The name of the project, as it appears in the database
|
* The name of the project, as it appears in the database
|
||||||
|
|
|
@ -36,6 +36,7 @@ removeProjectPath = base_url + "/api/removeProject"
|
||||||
promoteToPmPath = base_url + "/api/promoteToPm"
|
promoteToPmPath = base_url + "/api/promoteToPm"
|
||||||
unsignReportPath = base_url + "/api/unsignReport"
|
unsignReportPath = base_url + "/api/unsignReport"
|
||||||
deleteReportPath = base_url + "/api/deleteReport"
|
deleteReportPath = base_url + "/api/deleteReport"
|
||||||
|
getStatisticsPath = base_url + "/api/getStatistics"
|
||||||
|
|
||||||
debug_output = False
|
debug_output = False
|
||||||
|
|
||||||
|
@ -162,3 +163,11 @@ def deleteReport(report_id: int):
|
||||||
return requests.delete(
|
return requests.delete(
|
||||||
deleteReportPath + "/" + str(report_id),
|
deleteReportPath + "/" + str(report_id),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def getStatistics(token: string, projectName: string):
|
||||||
|
response = requests.get(
|
||||||
|
getStatisticsPath,
|
||||||
|
headers = {"Authorization": "Bearer " + token},
|
||||||
|
params={"projectName": projectName}
|
||||||
|
)
|
||||||
|
return response.json()
|
|
@ -625,6 +625,46 @@ def test_delete_report():
|
||||||
|
|
||||||
gprint("test_delete_report successful")
|
gprint("test_delete_report successful")
|
||||||
|
|
||||||
|
def test_get_statistics():
|
||||||
|
# Create admin
|
||||||
|
admin_username = randomString()
|
||||||
|
admin_password = randomString()
|
||||||
|
|
||||||
|
project_name = "project" + randomString()
|
||||||
|
|
||||||
|
token = register_and_login(admin_username, admin_password)
|
||||||
|
|
||||||
|
response = create_project(token, project_name)
|
||||||
|
assert response.status_code == 200, "Create project failed"
|
||||||
|
|
||||||
|
response = submitReport(token, {
|
||||||
|
"projectName": project_name,
|
||||||
|
"week": 1,
|
||||||
|
"developmentTime": 10,
|
||||||
|
"meetingTime": 5,
|
||||||
|
"adminTime": 5,
|
||||||
|
"ownWorkTime": 10,
|
||||||
|
"studyTime": 10,
|
||||||
|
"testingTime": 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
response = submitReport(token, {
|
||||||
|
"projectName": project_name,
|
||||||
|
"week": 2,
|
||||||
|
"developmentTime": 10,
|
||||||
|
"meetingTime": 5,
|
||||||
|
"adminTime": 5,
|
||||||
|
"ownWorkTime": 10,
|
||||||
|
"studyTime": 10,
|
||||||
|
"testingTime": 10,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert response.status_code == 200, "Submit report failed"
|
||||||
|
|
||||||
|
stats = getStatistics(token, project_name)
|
||||||
|
|
||||||
|
assert stats["totalDevelopmentTime"] == 20, "Total development time is not correct"
|
||||||
|
gprint("test_get_statistics successful")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -650,3 +690,4 @@ if __name__ == "__main__":
|
||||||
test_change_user_name()
|
test_change_user_name()
|
||||||
test_update_weekly_report()
|
test_update_weekly_report()
|
||||||
test_get_other_users_report_as_pm()
|
test_get_other_users_report_as_pm()
|
||||||
|
test_get_statistics()
|
||||||
|
|
Loading…
Reference in a new issue