Skip to content

Commit e8ccc2a

Browse files
authored
Merge pull request #306 from Worklenz/feature/task-activities-by-user
Feature/task activities by user
2 parents 01a580d + f24c0d8 commit e8ccc2a

18 files changed

Lines changed: 1076 additions & 176 deletions

File tree

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
import moment from "moment";
2+
import { IWorkLenzRequest } from "../interfaces/worklenz-request";
3+
import { IWorkLenzResponse } from "../interfaces/worklenz-response";
4+
5+
import db from "../config/db";
6+
7+
import { ServerResponse } from "../models/server-response";
8+
import WorklenzControllerBase from "./worklenz-controller-base";
9+
import HandleExceptions from "../decorators/handle-exceptions";
10+
import { formatDuration, formatLogText, getColor } from "../shared/utils";
11+
12+
interface IUserRecentTask {
13+
task_id: string;
14+
task_name: string;
15+
project_id: string;
16+
project_name: string;
17+
last_activity_at: string;
18+
activity_count: number;
19+
project_color?: string;
20+
task_status?: string;
21+
status_color?: string;
22+
}
23+
24+
interface IUserTimeLoggedTask {
25+
task_id: string;
26+
task_name: string;
27+
project_id: string;
28+
project_name: string;
29+
total_time_logged: number;
30+
total_time_logged_string: string;
31+
last_logged_at: string;
32+
logged_by_timer: boolean;
33+
project_color?: string;
34+
task_status?: string;
35+
status_color?: string;
36+
log_entries_count?: number;
37+
estimated_time?: number;
38+
}
39+
40+
export default class UserActivityLogsController extends WorklenzControllerBase {
41+
@HandleExceptions()
42+
public static async getRecentTasks(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
43+
if (!req.user) {
44+
return res.status(401).send(new ServerResponse(false, null, "Unauthorized"));
45+
}
46+
47+
const { id: userId, team_id: teamId } = req.user;
48+
const { offset = 0, limit = 10 } = req.query;
49+
50+
// Optimized query with better performance and team filtering
51+
const q = `
52+
SELECT DISTINCT tal.task_id, t.name AS task_name, tal.project_id, p.name AS project_name,
53+
MAX(tal.created_at) AS last_activity_at,
54+
COUNT(DISTINCT tal.id) AS activity_count,
55+
p.color_code AS project_color,
56+
(SELECT name FROM task_statuses WHERE id = t.status_id) AS task_status,
57+
(SELECT color_code
58+
FROM sys_task_status_categories
59+
WHERE id = (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color
60+
FROM task_activity_logs tal
61+
INNER JOIN tasks t ON tal.task_id = t.id AND t.archived = FALSE
62+
INNER JOIN projects p ON tal.project_id = p.id AND p.team_id = $1
63+
WHERE tal.user_id = $2
64+
AND tal.created_at >= NOW() - INTERVAL '30 days'
65+
GROUP BY tal.task_id, t.name, tal.project_id, p.name, p.color_code, t.status_id
66+
ORDER BY MAX(tal.created_at) DESC
67+
LIMIT $3 OFFSET $4;
68+
`;
69+
70+
const result = await db.query(q, [teamId, userId, limit, offset]);
71+
const tasks: IUserRecentTask[] = result.rows;
72+
73+
return res.status(200).send(new ServerResponse(true, tasks));
74+
}
75+
76+
@HandleExceptions()
77+
public static async getTimeLoggedTasks(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
78+
if (!req.user) {
79+
return res.status(401).send(new ServerResponse(false, null, "Unauthorized"));
80+
}
81+
82+
const { id: userId, team_id: teamId } = req.user;
83+
const { offset = 0, limit = 10 } = req.query;
84+
85+
// Optimized query with better performance, team filtering, and useful additional data
86+
const q = `
87+
SELECT twl.task_id, t.name AS task_name, t.project_id, p.name AS project_name,
88+
SUM(twl.time_spent) AS total_time_logged,
89+
MAX(twl.created_at) AS last_logged_at,
90+
MAX(twl.logged_by_timer::int)::boolean AS logged_by_timer,
91+
p.color_code AS project_color,
92+
(SELECT name FROM task_statuses WHERE id = t.status_id) AS task_status,
93+
(SELECT color_code
94+
FROM sys_task_status_categories
95+
WHERE id = (SELECT category_id FROM task_statuses WHERE id = t.status_id)) AS status_color,
96+
COUNT(DISTINCT twl.id) AS log_entries_count,
97+
(t.total_minutes * 60) AS estimated_time
98+
FROM task_work_log twl
99+
INNER JOIN tasks t ON twl.task_id = t.id AND t.archived = FALSE
100+
INNER JOIN projects p ON t.project_id = p.id AND p.team_id = $1
101+
WHERE twl.user_id = $2
102+
AND twl.created_at >= NOW() - INTERVAL '90 days'
103+
GROUP BY twl.task_id, t.name, t.project_id, p.name, p.color_code, t.status_id, t.total_minutes
104+
HAVING SUM(twl.time_spent) > 0
105+
ORDER BY MAX(twl.created_at) DESC
106+
LIMIT $3 OFFSET $4;
107+
`;
108+
109+
const result = await db.query(q, [teamId, userId, limit, offset]);
110+
const tasks: IUserTimeLoggedTask[] = result.rows.map(task => ({
111+
...task,
112+
total_time_logged_string: formatDuration(moment.duration(task.total_time_logged, "seconds")),
113+
}));
114+
115+
return res.status(200).send(new ServerResponse(true, tasks));
116+
}
117+
}
Lines changed: 125 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,122 +1,125 @@
1-
import express from "express";
2-
3-
import AccessControlsController from "../../controllers/access-controls-controller";
4-
import AuthController from "../../controllers/auth-controller";
5-
import LogsController from "../../controllers/logs-controller";
6-
import OverviewController from "../../controllers/overview-controller";
7-
import TaskPrioritiesController from "../../controllers/task-priorities-controller";
8-
9-
import attachmentsApiRouter from "./attachments-api-router";
10-
import clientsApiRouter from "./clients-api-router";
11-
import jobTitlesApiRouter from "./job-titles-api-router";
12-
import notificationsApiRouter from "./notifications-api-router";
13-
import personalOverviewApiRouter from "./personal-overview-api-router";
14-
import projectMembersApiRouter from "./project-members-api-router";
15-
import projectsApiRouter from "./projects-api-router";
16-
import settingsApiRouter from "./settings-api-router";
17-
import statusesApiRouter from "./statuses-api-router";
18-
import subTasksApiRouter from "./sub-tasks-api-router";
19-
import taskCommentsApiRouter from "./task-comments-api-router";
20-
import taskWorkLogApiRouter from "./task-work-log-api-router";
21-
import tasksApiRouter from "./tasks-api-router";
22-
import teamMembersApiRouter from "./team-members-api-router";
23-
import teamsApiRouter from "./teams-api-router";
24-
import timezonesApiRouter from "./timezones-api-router";
25-
import todoListApiRouter from "./todo-list-api-router";
26-
import projectStatusesApiRouter from "./project-statuses-api-router";
27-
import labelsApiRouter from "./labels-api-router";
28-
import sharedProjectsApiRouter from "./shared-projects-api-router";
29-
import resourceAllocationApiRouter from "./resource-allocation-api-router";
30-
import taskTemplatesApiRouter from "./task-templates-api-router";
31-
import projectInsightsApiRouter from "./project-insights-api-router";
32-
import passwordValidator from "../../middlewares/validators/password-validator";
33-
import adminCenterApiRouter from "./admin-center-api-router";
34-
import reportingApiRouter from "./reporting-api-router";
35-
import activityLogsApiRouter from "./activity-logs-api-router";
36-
import safeControllerFunction from "../../shared/safe-controller-function";
37-
import projectFoldersApiRouter from "./project-folders-api-router";
38-
import taskPhasesApiRouter from "./task-phases-api-router";
39-
import projectCategoriesApiRouter from "./project-categories-api-router";
40-
import homePageApiRouter from "./home-page-api-router";
41-
import ganttApiRouter from "./gantt-api-router";
42-
import projectCommentsApiRouter from "./project-comments-api-router";
43-
import reportingExportApiRouter from "./reporting-export-api-router";
44-
import projectHealthsApiRouter from "./project-healths-api-router";
45-
import ptTasksApiRouter from "./pt-tasks-api-router";
46-
import projectTemplatesApiRouter from "./project-templates-api";
47-
import ptTaskPhasesApiRouter from "./pt_task-phases-api-router";
48-
import ptStatusesApiRouter from "./pt-statuses-api-router";
49-
import workloadApiRouter from "./gannt-apis/workload-api-router";
50-
import roadmapApiRouter from "./gannt-apis/roadmap-api-router";
51-
import scheduleApiRouter from "./gannt-apis/schedule-api-router";
52-
import scheduleApiV2Router from "./gannt-apis/schedule-api-v2-router";
53-
import projectManagerApiRouter from "./project-managers-api-router";
54-
import surveyApiRouter from "./survey-api-router";
55-
56-
import billingApiRouter from "./billing-api-router";
57-
import taskDependenciesApiRouter from "./task-dependencies-api-router";
58-
59-
import taskRecurringApiRouter from "./task-recurring-api-router";
60-
import customColumnsApiRouter from "./custom-columns-api-router";
61-
62-
const api = express.Router();
63-
64-
api.use("/projects", projectsApiRouter);
65-
api.use("/team-members", teamMembersApiRouter);
66-
api.use("/job-titles", jobTitlesApiRouter);
67-
api.use("/clients", clientsApiRouter);
68-
api.use("/teams", teamsApiRouter);
69-
api.use("/tasks", tasksApiRouter);
70-
api.use("/settings", settingsApiRouter);
71-
api.use("/personal-overview", personalOverviewApiRouter);
72-
api.use("/statuses", statusesApiRouter);
73-
api.use("/todo-list", todoListApiRouter);
74-
api.use("/notifications", notificationsApiRouter);
75-
api.use("/attachments", attachmentsApiRouter);
76-
api.use("/sub-tasks", subTasksApiRouter);
77-
api.use("/project-members", projectMembersApiRouter);
78-
api.use("/task-time-log", taskWorkLogApiRouter);
79-
api.use("/task-comments", taskCommentsApiRouter);
80-
api.use("/timezones", timezonesApiRouter);
81-
api.use("/project-statuses", projectStatusesApiRouter);
82-
api.use("/labels", labelsApiRouter);
83-
api.use("/resource-allocation", resourceAllocationApiRouter);
84-
api.use("/shared/projects", sharedProjectsApiRouter);
85-
api.use("/task-templates", taskTemplatesApiRouter);
86-
api.use("/project-insights", projectInsightsApiRouter);
87-
api.use("/admin-center", adminCenterApiRouter);
88-
api.use("/reporting", reportingApiRouter);
89-
api.use("/activity-logs", activityLogsApiRouter);
90-
api.use("/projects-folders", projectFoldersApiRouter);
91-
api.use("/task-phases", taskPhasesApiRouter);
92-
api.use("/project-categories", projectCategoriesApiRouter);
93-
api.use("/home", homePageApiRouter);
94-
api.use("/gantt", ganttApiRouter);
95-
api.use("/project-comments", projectCommentsApiRouter);
96-
api.use("/reporting-export", reportingExportApiRouter);
97-
api.use("/project-healths", projectHealthsApiRouter);
98-
api.use("/project-templates", projectTemplatesApiRouter);
99-
api.use("/pt-tasks", ptTasksApiRouter);
100-
api.use("/pt-task-phases", ptTaskPhasesApiRouter);
101-
api.use("/pt-statuses", ptStatusesApiRouter);
102-
api.use("/workload-gannt", workloadApiRouter);
103-
api.use("/roadmap-gannt", roadmapApiRouter);
104-
api.use("/schedule-gannt", scheduleApiRouter);
105-
api.use("/schedule-gannt-v2", scheduleApiV2Router);
106-
api.use("/project-managers", projectManagerApiRouter);
107-
api.use("/surveys", surveyApiRouter);
108-
109-
api.get("/overview/:id", safeControllerFunction(OverviewController.getById));
110-
api.get("/task-priorities", safeControllerFunction(TaskPrioritiesController.get));
111-
api.post("/change-password", passwordValidator, safeControllerFunction(AuthController.changePassword));
112-
api.get("/access-controls/roles", safeControllerFunction(AccessControlsController.getRoles));
113-
api.get("/logs/my-dashboard", safeControllerFunction(LogsController.getActivityLog));
114-
115-
api.use("/billing", billingApiRouter);
116-
api.use("/task-dependencies", taskDependenciesApiRouter);
117-
118-
api.use("/task-recurring", taskRecurringApiRouter);
119-
120-
api.use("/custom-columns", customColumnsApiRouter);
121-
122-
export default api;
1+
import express from "express";
2+
3+
import AccessControlsController from "../../controllers/access-controls-controller";
4+
import AuthController from "../../controllers/auth-controller";
5+
import LogsController from "../../controllers/logs-controller";
6+
import OverviewController from "../../controllers/overview-controller";
7+
import TaskPrioritiesController from "../../controllers/task-priorities-controller";
8+
9+
import attachmentsApiRouter from "./attachments-api-router";
10+
import clientsApiRouter from "./clients-api-router";
11+
import jobTitlesApiRouter from "./job-titles-api-router";
12+
import notificationsApiRouter from "./notifications-api-router";
13+
import personalOverviewApiRouter from "./personal-overview-api-router";
14+
import projectMembersApiRouter from "./project-members-api-router";
15+
import projectsApiRouter from "./projects-api-router";
16+
import settingsApiRouter from "./settings-api-router";
17+
import statusesApiRouter from "./statuses-api-router";
18+
import subTasksApiRouter from "./sub-tasks-api-router";
19+
import taskCommentsApiRouter from "./task-comments-api-router";
20+
import taskWorkLogApiRouter from "./task-work-log-api-router";
21+
import tasksApiRouter from "./tasks-api-router";
22+
import teamMembersApiRouter from "./team-members-api-router";
23+
import teamsApiRouter from "./teams-api-router";
24+
import timezonesApiRouter from "./timezones-api-router";
25+
import todoListApiRouter from "./todo-list-api-router";
26+
import projectStatusesApiRouter from "./project-statuses-api-router";
27+
import labelsApiRouter from "./labels-api-router";
28+
import sharedProjectsApiRouter from "./shared-projects-api-router";
29+
import resourceAllocationApiRouter from "./resource-allocation-api-router";
30+
import taskTemplatesApiRouter from "./task-templates-api-router";
31+
import projectInsightsApiRouter from "./project-insights-api-router";
32+
import passwordValidator from "../../middlewares/validators/password-validator";
33+
import adminCenterApiRouter from "./admin-center-api-router";
34+
import reportingApiRouter from "./reporting-api-router";
35+
import activityLogsApiRouter from "./activity-logs-api-router";
36+
import safeControllerFunction from "../../shared/safe-controller-function";
37+
import projectFoldersApiRouter from "./project-folders-api-router";
38+
import taskPhasesApiRouter from "./task-phases-api-router";
39+
import projectCategoriesApiRouter from "./project-categories-api-router";
40+
import homePageApiRouter from "./home-page-api-router";
41+
import ganttApiRouter from "./gantt-api-router";
42+
import projectCommentsApiRouter from "./project-comments-api-router";
43+
import reportingExportApiRouter from "./reporting-export-api-router";
44+
import projectHealthsApiRouter from "./project-healths-api-router";
45+
import ptTasksApiRouter from "./pt-tasks-api-router";
46+
import projectTemplatesApiRouter from "./project-templates-api";
47+
import ptTaskPhasesApiRouter from "./pt_task-phases-api-router";
48+
import ptStatusesApiRouter from "./pt-statuses-api-router";
49+
import workloadApiRouter from "./gannt-apis/workload-api-router";
50+
import roadmapApiRouter from "./gannt-apis/roadmap-api-router";
51+
import scheduleApiRouter from "./gannt-apis/schedule-api-router";
52+
import scheduleApiV2Router from "./gannt-apis/schedule-api-v2-router";
53+
import projectManagerApiRouter from "./project-managers-api-router";
54+
import surveyApiRouter from "./survey-api-router";
55+
56+
import billingApiRouter from "./billing-api-router";
57+
import taskDependenciesApiRouter from "./task-dependencies-api-router";
58+
59+
import taskRecurringApiRouter from "./task-recurring-api-router";
60+
61+
import customColumnsApiRouter from "./custom-columns-api-router";
62+
import userActivityLogsApiRouter from "./user-activity-logs-api-router";
63+
64+
const api = express.Router();
65+
66+
api.use("/projects", projectsApiRouter);
67+
api.use("/team-members", teamMembersApiRouter);
68+
api.use("/job-titles", jobTitlesApiRouter);
69+
api.use("/clients", clientsApiRouter);
70+
api.use("/teams", teamsApiRouter);
71+
api.use("/tasks", tasksApiRouter);
72+
api.use("/settings", settingsApiRouter);
73+
api.use("/personal-overview", personalOverviewApiRouter);
74+
api.use("/statuses", statusesApiRouter);
75+
api.use("/todo-list", todoListApiRouter);
76+
api.use("/notifications", notificationsApiRouter);
77+
api.use("/attachments", attachmentsApiRouter);
78+
api.use("/sub-tasks", subTasksApiRouter);
79+
api.use("/project-members", projectMembersApiRouter);
80+
api.use("/task-time-log", taskWorkLogApiRouter);
81+
api.use("/task-comments", taskCommentsApiRouter);
82+
api.use("/timezones", timezonesApiRouter);
83+
api.use("/project-statuses", projectStatusesApiRouter);
84+
api.use("/labels", labelsApiRouter);
85+
api.use("/resource-allocation", resourceAllocationApiRouter);
86+
api.use("/shared/projects", sharedProjectsApiRouter);
87+
api.use("/task-templates", taskTemplatesApiRouter);
88+
api.use("/project-insights", projectInsightsApiRouter);
89+
api.use("/admin-center", adminCenterApiRouter);
90+
api.use("/reporting", reportingApiRouter);
91+
api.use("/activity-logs", activityLogsApiRouter);
92+
api.use("/projects-folders", projectFoldersApiRouter);
93+
api.use("/task-phases", taskPhasesApiRouter);
94+
api.use("/project-categories", projectCategoriesApiRouter);
95+
api.use("/home", homePageApiRouter);
96+
api.use("/gantt", ganttApiRouter);
97+
api.use("/project-comments", projectCommentsApiRouter);
98+
api.use("/reporting-export", reportingExportApiRouter);
99+
api.use("/project-healths", projectHealthsApiRouter);
100+
api.use("/project-templates", projectTemplatesApiRouter);
101+
api.use("/pt-tasks", ptTasksApiRouter);
102+
api.use("/pt-task-phases", ptTaskPhasesApiRouter);
103+
api.use("/pt-statuses", ptStatusesApiRouter);
104+
api.use("/workload-gannt", workloadApiRouter);
105+
api.use("/roadmap-gannt", roadmapApiRouter);
106+
api.use("/schedule-gannt", scheduleApiRouter);
107+
api.use("/schedule-gannt-v2", scheduleApiV2Router);
108+
api.use("/project-managers", projectManagerApiRouter);
109+
api.use("/surveys", surveyApiRouter);
110+
111+
api.get("/overview/:id", safeControllerFunction(OverviewController.getById));
112+
api.get("/task-priorities", safeControllerFunction(TaskPrioritiesController.get));
113+
api.post("/change-password", passwordValidator, safeControllerFunction(AuthController.changePassword));
114+
api.get("/access-controls/roles", safeControllerFunction(AccessControlsController.getRoles));
115+
api.get("/logs/my-dashboard", safeControllerFunction(LogsController.getActivityLog));
116+
117+
api.use("/billing", billingApiRouter);
118+
api.use("/task-dependencies", taskDependenciesApiRouter);
119+
120+
api.use("/task-recurring", taskRecurringApiRouter);
121+
122+
api.use("/custom-columns", customColumnsApiRouter);
123+
124+
api.use("/logs", userActivityLogsApiRouter);
125+
export default api;
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import express from 'express';
2+
3+
import UserActivityLogsController from '../../controllers/user-activity-logs-controller';
4+
import safeControllerFunction from "../../shared/safe-controller-function";
5+
6+
const userActivityLogsApiRouter = express.Router();
7+
8+
userActivityLogsApiRouter.get('/user-recent-tasks', safeControllerFunction(UserActivityLogsController.getRecentTasks));
9+
userActivityLogsApiRouter.get('/user-time-logged-tasks', safeControllerFunction(UserActivityLogsController.getTimeLoggedTasks));
10+
11+
export default userActivityLogsApiRouter;

worklenz-frontend/public/locales/alb/home.json

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,18 @@
4141
"list": "Listë",
4242
"calendar": "Kalendar",
4343
"tasks": "Detyrat",
44-
"refresh": "Rifresko"
44+
"refresh": "Rifresko",
45+
"recentActivity": "Aktiviteti i Fundit",
46+
"recentTasks": "Detyrat e Fundit",
47+
"timeLoggedTasks": "Koha e Regjistruar",
48+
"noRecentTasks": "Asnjë detyrë e fundit",
49+
"noTimeLoggedTasks": "Asnjë detyrë me kohë të regjistruar",
50+
"activityTag": "Aktiviteti",
51+
"timeLogTag": "Regjistrim Kohe",
52+
"timerTag": "Kohëmatës",
53+
"activitySingular": "aktivitet",
54+
"activityPlural": "aktivitete",
55+
"recentTaskAriaLabel": "Detyrë e fundit:",
56+
"timeLoggedTaskAriaLabel": "Detyrë me kohë të regjistruar:"
4557
}
4658
}

0 commit comments

Comments
 (0)