Add task progress tracking methods and enhance UI components
- Introduced a comprehensive guide for users on task progress tracking methods, including manual, weighted, and time-based progress. - Implemented backend support for progress calculations, including SQL functions and migrations to accommodate new progress features. - Enhanced frontend components to support progress input and display, including updates to task and project drawers. - Added localization for new progress-related terms and validation messages. - Integrated real-time updates for task progress and weight changes through socket events.
This commit is contained in:
@@ -515,6 +515,142 @@ BEGIN
|
||||
END
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.get_task_form_view_model(_user_id UUID, _team_id UUID, _task_id UUID, _project_id UUID) RETURNS JSON
|
||||
LANGUAGE plpgsql
|
||||
AS
|
||||
$$
|
||||
DECLARE
|
||||
_task JSON;
|
||||
_priorities JSON;
|
||||
_projects JSON;
|
||||
_statuses JSON;
|
||||
_team_members JSON;
|
||||
_assignees JSON;
|
||||
_phases JSON;
|
||||
BEGIN
|
||||
|
||||
-- Select task info
|
||||
SELECT COALESCE(ROW_TO_JSON(rec), '{}'::JSON)
|
||||
INTO _task
|
||||
FROM (WITH RECURSIVE task_hierarchy AS (
|
||||
-- Base case: Start with the given task
|
||||
SELECT id,
|
||||
parent_task_id,
|
||||
0 AS level
|
||||
FROM tasks
|
||||
WHERE id = _task_id
|
||||
|
||||
UNION ALL
|
||||
|
||||
-- Recursive case: Traverse up to parent tasks
|
||||
SELECT t.id,
|
||||
t.parent_task_id,
|
||||
th.level + 1 AS level
|
||||
FROM tasks t
|
||||
INNER JOIN task_hierarchy th ON t.id = th.parent_task_id
|
||||
WHERE th.parent_task_id IS NOT NULL)
|
||||
SELECT id,
|
||||
name,
|
||||
description,
|
||||
start_date,
|
||||
end_date,
|
||||
done,
|
||||
total_minutes,
|
||||
priority_id,
|
||||
project_id,
|
||||
created_at,
|
||||
updated_at,
|
||||
status_id,
|
||||
parent_task_id,
|
||||
sort_order,
|
||||
(SELECT phase_id FROM task_phase WHERE task_id = tasks.id) AS phase_id,
|
||||
CONCAT((SELECT key FROM projects WHERE id = tasks.project_id), '-', task_no) AS task_key,
|
||||
(SELECT start_time
|
||||
FROM task_timers
|
||||
WHERE task_id = tasks.id
|
||||
AND user_id = _user_id) AS timer_start_time,
|
||||
parent_task_id IS NOT NULL AS is_sub_task,
|
||||
(SELECT COUNT('*')
|
||||
FROM tasks
|
||||
WHERE parent_task_id = tasks.id
|
||||
AND archived IS FALSE) AS sub_tasks_count,
|
||||
(SELECT COUNT(*)
|
||||
FROM tasks_with_status_view tt
|
||||
WHERE (tt.parent_task_id = tasks.id OR tt.task_id = tasks.id)
|
||||
AND tt.is_done IS TRUE)
|
||||
AS completed_count,
|
||||
(SELECT COUNT(*) FROM task_attachments WHERE task_id = tasks.id) AS attachments_count,
|
||||
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(r))), '[]'::JSON)
|
||||
FROM (SELECT task_labels.label_id AS id,
|
||||
(SELECT name FROM team_labels WHERE id = task_labels.label_id),
|
||||
(SELECT color_code FROM team_labels WHERE id = task_labels.label_id)
|
||||
FROM task_labels
|
||||
WHERE task_id = tasks.id
|
||||
ORDER BY name) r) AS labels,
|
||||
(SELECT color_code
|
||||
FROM sys_task_status_categories
|
||||
WHERE id = (SELECT category_id FROM task_statuses WHERE id = tasks.status_id)) AS status_color,
|
||||
(SELECT COUNT(*) FROM tasks WHERE parent_task_id = _task_id) AS sub_tasks_count,
|
||||
(SELECT name FROM users WHERE id = tasks.reporter_id) AS reporter,
|
||||
(SELECT get_task_assignees(tasks.id)) AS assignees,
|
||||
(SELECT id FROM team_members WHERE user_id = _user_id AND team_id = _team_id) AS team_member_id,
|
||||
billable,
|
||||
schedule_id,
|
||||
progress_value,
|
||||
weight,
|
||||
(SELECT MAX(level) FROM task_hierarchy) AS task_level
|
||||
FROM tasks
|
||||
WHERE id = _task_id) rec;
|
||||
|
||||
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
|
||||
INTO _priorities
|
||||
FROM (SELECT id, name FROM task_priorities ORDER BY value) rec;
|
||||
|
||||
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
|
||||
INTO _phases
|
||||
FROM (SELECT id, name FROM project_phases WHERE project_id = _project_id ORDER BY name) rec;
|
||||
|
||||
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
|
||||
INTO _projects
|
||||
FROM (SELECT id, name
|
||||
FROM projects
|
||||
WHERE team_id = _team_id
|
||||
AND (CASE
|
||||
WHEN (is_owner(_user_id, _team_id) OR is_admin(_user_id, _team_id) IS TRUE) THEN TRUE
|
||||
ELSE is_member_of_project(projects.id, _user_id, _team_id) END)
|
||||
ORDER BY name) rec;
|
||||
|
||||
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
|
||||
INTO _statuses
|
||||
FROM (SELECT id, name FROM task_statuses WHERE project_id = _project_id) rec;
|
||||
|
||||
SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
|
||||
INTO _team_members
|
||||
FROM (SELECT team_members.id,
|
||||
(SELECT name FROM team_member_info_view WHERE team_member_info_view.team_member_id = team_members.id),
|
||||
(SELECT email FROM team_member_info_view WHERE team_member_info_view.team_member_id = team_members.id),
|
||||
(SELECT avatar_url
|
||||
FROM team_member_info_view
|
||||
WHERE team_member_info_view.team_member_id = team_members.id)
|
||||
FROM team_members
|
||||
LEFT JOIN users u ON team_members.user_id = u.id
|
||||
WHERE team_id = _team_id
|
||||
AND team_members.active IS TRUE) rec;
|
||||
|
||||
SELECT get_task_assignees(_task_id) INTO _assignees;
|
||||
|
||||
RETURN JSON_BUILD_OBJECT(
|
||||
'task', _task,
|
||||
'priorities', _priorities,
|
||||
'projects', _projects,
|
||||
'statuses', _statuses,
|
||||
'team_members', _team_members,
|
||||
'assignees', _assignees,
|
||||
'phases', _phases
|
||||
);
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Add use_manual_progress, use_weighted_progress, and use_time_progress to projects table if they don't exist
|
||||
ALTER TABLE projects
|
||||
ADD COLUMN IF NOT EXISTS use_manual_progress BOOLEAN DEFAULT FALSE,
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
-- Migration: Add progress and weight activity types support
|
||||
-- Date: 2025-04-24
|
||||
-- Version: 1.0.0
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- Update the get_activity_logs_by_task function to handle progress and weight attribute types
|
||||
CREATE OR REPLACE FUNCTION get_activity_logs_by_task(_task_id uuid) RETURNS json
|
||||
LANGUAGE plpgsql
|
||||
AS
|
||||
$$
|
||||
DECLARE
|
||||
_result JSON;
|
||||
BEGIN
|
||||
SELECT ROW_TO_JSON(rec)
|
||||
INTO _result
|
||||
FROM (SELECT (SELECT tasks.created_at FROM tasks WHERE tasks.id = _task_id),
|
||||
(SELECT name
|
||||
FROM users
|
||||
WHERE id = (SELECT reporter_id FROM tasks WHERE id = _task_id)),
|
||||
(SELECT avatar_url
|
||||
FROM users
|
||||
WHERE id = (SELECT reporter_id FROM tasks WHERE id = _task_id)),
|
||||
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec2))), '[]'::JSON)
|
||||
FROM (SELECT task_id,
|
||||
created_at,
|
||||
attribute_type,
|
||||
log_type,
|
||||
|
||||
-- Case for previous value
|
||||
(CASE
|
||||
WHEN (attribute_type = 'status')
|
||||
THEN (SELECT name FROM task_statuses WHERE id = old_value::UUID)
|
||||
WHEN (attribute_type = 'priority')
|
||||
THEN (SELECT name FROM task_priorities WHERE id = old_value::UUID)
|
||||
WHEN (attribute_type = 'phase' AND old_value <> 'Unmapped')
|
||||
THEN (SELECT name FROM project_phases WHERE id = old_value::UUID)
|
||||
WHEN (attribute_type = 'progress' OR attribute_type = 'weight')
|
||||
THEN old_value
|
||||
ELSE (old_value) END) AS previous,
|
||||
|
||||
-- Case for current value
|
||||
(CASE
|
||||
WHEN (attribute_type = 'assignee')
|
||||
THEN (SELECT name FROM users WHERE id = new_value::UUID)
|
||||
WHEN (attribute_type = 'label')
|
||||
THEN (SELECT name FROM team_labels WHERE id = new_value::UUID)
|
||||
WHEN (attribute_type = 'status')
|
||||
THEN (SELECT name FROM task_statuses WHERE id = new_value::UUID)
|
||||
WHEN (attribute_type = 'priority')
|
||||
THEN (SELECT name FROM task_priorities WHERE id = new_value::UUID)
|
||||
WHEN (attribute_type = 'phase' AND new_value <> 'Unmapped')
|
||||
THEN (SELECT name FROM project_phases WHERE id = new_value::UUID)
|
||||
WHEN (attribute_type = 'progress' OR attribute_type = 'weight')
|
||||
THEN new_value
|
||||
ELSE (new_value) END) AS current,
|
||||
|
||||
-- Case for assigned user
|
||||
(CASE
|
||||
WHEN (attribute_type = 'assignee')
|
||||
THEN (SELECT ROW_TO_JSON(rec)
|
||||
FROM (SELECT (CASE
|
||||
WHEN (new_value IS NOT NULL)
|
||||
THEN (SELECT name FROM users WHERE users.id = new_value::UUID)
|
||||
ELSE (next_string) END) AS name,
|
||||
(SELECT avatar_url FROM users WHERE users.id = new_value::UUID)) rec)
|
||||
ELSE (NULL) END) AS assigned_user,
|
||||
|
||||
-- Case for label data
|
||||
(CASE
|
||||
WHEN (attribute_type = 'label')
|
||||
THEN (SELECT ROW_TO_JSON(rec)
|
||||
FROM (SELECT (SELECT name FROM team_labels WHERE id = new_value::UUID),
|
||||
(SELECT color_code FROM team_labels WHERE id = new_value::UUID)) rec)
|
||||
ELSE (NULL) END) AS label_data,
|
||||
|
||||
-- Case for previous status
|
||||
(CASE
|
||||
WHEN (attribute_type = 'status')
|
||||
THEN (SELECT ROW_TO_JSON(rec)
|
||||
FROM (SELECT (SELECT name FROM task_statuses WHERE id = old_value::UUID),
|
||||
(SELECT color_code
|
||||
FROM sys_task_status_categories
|
||||
WHERE id = (SELECT category_id FROM task_statuses WHERE id = old_value::UUID)),
|
||||
(SELECT color_code_dark
|
||||
FROM sys_task_status_categories
|
||||
WHERE id = (SELECT category_id FROM task_statuses WHERE id = old_value::UUID))) rec)
|
||||
ELSE (NULL) END) AS previous_status,
|
||||
|
||||
-- Case for next status
|
||||
(CASE
|
||||
WHEN (attribute_type = 'status')
|
||||
THEN (SELECT ROW_TO_JSON(rec)
|
||||
FROM (SELECT (SELECT name FROM task_statuses WHERE id = new_value::UUID),
|
||||
(SELECT color_code
|
||||
FROM sys_task_status_categories
|
||||
WHERE id = (SELECT category_id FROM task_statuses WHERE id = new_value::UUID)),
|
||||
(SELECT color_code_dark
|
||||
FROM sys_task_status_categories
|
||||
WHERE id = (SELECT category_id FROM task_statuses WHERE id = new_value::UUID))) rec)
|
||||
ELSE (NULL) END) AS next_status,
|
||||
|
||||
-- Case for previous priority
|
||||
(CASE
|
||||
WHEN (attribute_type = 'priority')
|
||||
THEN (SELECT ROW_TO_JSON(rec)
|
||||
FROM (SELECT (SELECT name FROM task_priorities WHERE id = old_value::UUID),
|
||||
(SELECT color_code FROM task_priorities WHERE id = old_value::UUID)) rec)
|
||||
ELSE (NULL) END) AS previous_priority,
|
||||
|
||||
-- Case for next priority
|
||||
(CASE
|
||||
WHEN (attribute_type = 'priority')
|
||||
THEN (SELECT ROW_TO_JSON(rec)
|
||||
FROM (SELECT (SELECT name FROM task_priorities WHERE id = new_value::UUID),
|
||||
(SELECT color_code FROM task_priorities WHERE id = new_value::UUID)) rec)
|
||||
ELSE (NULL) END) AS next_priority,
|
||||
|
||||
-- Case for previous phase
|
||||
(CASE
|
||||
WHEN (attribute_type = 'phase' AND old_value <> 'Unmapped')
|
||||
THEN (SELECT ROW_TO_JSON(rec)
|
||||
FROM (SELECT (SELECT name FROM project_phases WHERE id = old_value::UUID),
|
||||
(SELECT color_code FROM project_phases WHERE id = old_value::UUID)) rec)
|
||||
ELSE (NULL) END) AS previous_phase,
|
||||
|
||||
-- Case for next phase
|
||||
(CASE
|
||||
WHEN (attribute_type = 'phase' AND new_value <> 'Unmapped')
|
||||
THEN (SELECT ROW_TO_JSON(rec)
|
||||
FROM (SELECT (SELECT name FROM project_phases WHERE id = new_value::UUID),
|
||||
(SELECT color_code FROM project_phases WHERE id = new_value::UUID)) rec)
|
||||
ELSE (NULL) END) AS next_phase,
|
||||
|
||||
-- Case for done by
|
||||
(SELECT ROW_TO_JSON(rec)
|
||||
FROM (SELECT (SELECT name FROM users WHERE users.id = tal.user_id),
|
||||
(SELECT avatar_url FROM users WHERE users.id = tal.user_id)) rec) AS done_by,
|
||||
|
||||
-- Add log text for progress and weight
|
||||
(CASE
|
||||
WHEN (attribute_type = 'progress')
|
||||
THEN 'updated the progress of'
|
||||
WHEN (attribute_type = 'weight')
|
||||
THEN 'updated the weight of'
|
||||
ELSE ''
|
||||
END) AS log_text
|
||||
|
||||
|
||||
FROM task_activity_logs tal
|
||||
WHERE task_id = _task_id
|
||||
ORDER BY created_at DESC) rec2) AS logs) rec;
|
||||
RETURN _result;
|
||||
END;
|
||||
$$;
|
||||
|
||||
COMMIT;
|
||||
@@ -32,7 +32,51 @@ export default class TasksControllerBase extends WorklenzControllerBase {
|
||||
}
|
||||
|
||||
public static updateTaskViewModel(task: any) {
|
||||
task.progress = ~~(task.total_minutes_spent / task.total_minutes * 100);
|
||||
console.log(`Processing task ${task.id} (${task.name})`);
|
||||
console.log(` manual_progress: ${task.manual_progress}, progress_value: ${task.progress_value}`);
|
||||
console.log(` project_use_manual_progress: ${task.project_use_manual_progress}, project_use_weighted_progress: ${task.project_use_weighted_progress}`);
|
||||
console.log(` has subtasks: ${task.sub_tasks_count > 0}`);
|
||||
|
||||
// For parent tasks (with subtasks), always use calculated progress from subtasks
|
||||
if (task.sub_tasks_count > 0) {
|
||||
// For parent tasks without manual progress, calculate from subtasks (already done via db function)
|
||||
console.log(` Parent task with subtasks: complete_ratio=${task.complete_ratio}`);
|
||||
|
||||
// Ensure progress matches complete_ratio for consistency
|
||||
task.progress = task.complete_ratio || 0;
|
||||
|
||||
// Important: Parent tasks should not have manual progress
|
||||
// If they somehow do, reset it
|
||||
if (task.manual_progress) {
|
||||
console.log(` WARNING: Parent task ${task.id} had manual_progress set to true, resetting`);
|
||||
task.manual_progress = false;
|
||||
task.progress_value = null;
|
||||
}
|
||||
}
|
||||
// For tasks without subtasks, respect manual progress if set
|
||||
else if (task.manual_progress === true && task.progress_value !== null && task.progress_value !== undefined) {
|
||||
// For manually set progress, use that value directly
|
||||
task.progress = parseInt(task.progress_value);
|
||||
task.complete_ratio = parseInt(task.progress_value);
|
||||
|
||||
console.log(` Using manual progress: progress=${task.progress}, complete_ratio=${task.complete_ratio}`);
|
||||
}
|
||||
// For tasks with no subtasks and no manual progress, calculate based on time
|
||||
else {
|
||||
task.progress = task.total_minutes_spent && task.total_minutes
|
||||
? ~~(task.total_minutes_spent / task.total_minutes * 100)
|
||||
: 0;
|
||||
|
||||
// Set complete_ratio to match progress
|
||||
task.complete_ratio = task.progress;
|
||||
|
||||
console.log(` Calculated time-based progress: progress=${task.progress}, complete_ratio=${task.complete_ratio}`);
|
||||
}
|
||||
|
||||
// Ensure numeric values
|
||||
task.progress = parseInt(task.progress) || 0;
|
||||
task.complete_ratio = parseInt(task.complete_ratio) || 0;
|
||||
|
||||
task.overdue = task.total_minutes < task.total_minutes_spent;
|
||||
|
||||
task.time_spent = {hours: ~~(task.total_minutes_spent / 60), minutes: task.total_minutes_spent % 60};
|
||||
@@ -73,9 +117,9 @@ export default class TasksControllerBase extends WorklenzControllerBase {
|
||||
if (task.timer_start_time)
|
||||
task.timer_start_time = moment(task.timer_start_time).valueOf();
|
||||
|
||||
// Set completed_count and total_tasks_count regardless of progress calculation method
|
||||
const totalCompleted = (+task.completed_sub_tasks + +task.parent_task_completed) || 0;
|
||||
const totalTasks = +task.sub_tasks_count || 0; // if needed add +1 for parent
|
||||
task.complete_ratio = TasksControllerBase.calculateTaskCompleteRatio(totalCompleted, totalTasks);
|
||||
const totalTasks = +task.sub_tasks_count || 0;
|
||||
task.completed_count = totalCompleted;
|
||||
task.total_tasks_count = totalTasks;
|
||||
|
||||
|
||||
@@ -192,6 +192,12 @@ export default class TasksControllerV2 extends TasksControllerBase {
|
||||
t.archived,
|
||||
t.description,
|
||||
t.sort_order,
|
||||
t.progress_value,
|
||||
t.manual_progress,
|
||||
t.weight,
|
||||
(SELECT use_manual_progress FROM projects WHERE id = t.project_id) AS project_use_manual_progress,
|
||||
(SELECT use_weighted_progress FROM projects WHERE id = t.project_id) AS project_use_weighted_progress,
|
||||
(SELECT use_time_progress FROM projects WHERE id = t.project_id) AS project_use_time_progress,
|
||||
|
||||
(SELECT phase_id FROM task_phase WHERE task_id = t.id) AS phase_id,
|
||||
(SELECT name
|
||||
@@ -334,7 +340,7 @@ export default class TasksControllerV2 extends TasksControllerBase {
|
||||
return g;
|
||||
}, {});
|
||||
|
||||
this.updateMapByGroup(tasks, groupBy, map);
|
||||
await this.updateMapByGroup(tasks, groupBy, map);
|
||||
|
||||
const updatedGroups = Object.keys(map).map(key => {
|
||||
const group = map[key];
|
||||
@@ -353,11 +359,27 @@ export default class TasksControllerV2 extends TasksControllerBase {
|
||||
return res.status(200).send(new ServerResponse(true, updatedGroups));
|
||||
}
|
||||
|
||||
public static updateMapByGroup(tasks: any[], groupBy: string, map: { [p: string]: ITaskGroup }) {
|
||||
public static async updateMapByGroup(tasks: any[], groupBy: string, map: { [p: string]: ITaskGroup }) {
|
||||
let index = 0;
|
||||
const unmapped = [];
|
||||
for (const task of tasks) {
|
||||
task.index = index++;
|
||||
|
||||
// For tasks with subtasks, get the complete ratio from the database function
|
||||
if (task.sub_tasks_count > 0) {
|
||||
try {
|
||||
const result = await db.query("SELECT get_task_complete_ratio($1) AS info;", [task.id]);
|
||||
const [data] = result.rows;
|
||||
if (data && data.info) {
|
||||
task.complete_ratio = +data.info.ratio.toFixed();
|
||||
task.completed_count = data.info.total_completed;
|
||||
task.total_tasks_count = data.info.total_tasks;
|
||||
}
|
||||
} catch (error) {
|
||||
// Proceed with default calculation if database call fails
|
||||
}
|
||||
}
|
||||
|
||||
TasksControllerV2.updateTaskViewModel(task);
|
||||
if (groupBy === GroupBy.STATUS) {
|
||||
map[task.status]?.tasks.push(task);
|
||||
@@ -395,7 +417,7 @@ export default class TasksControllerV2 extends TasksControllerBase {
|
||||
@HandleExceptions()
|
||||
public static async getTasksOnly(req: IWorkLenzRequest, res: IWorkLenzResponse): Promise<IWorkLenzResponse> {
|
||||
const isSubTasks = !!req.query.parent_task;
|
||||
|
||||
|
||||
// Add customColumns flag to query params
|
||||
req.query.customColumns = "true";
|
||||
|
||||
@@ -410,7 +432,24 @@ export default class TasksControllerV2 extends TasksControllerBase {
|
||||
[data] = result.rows;
|
||||
} else { // else we return a flat list of tasks
|
||||
data = [...result.rows];
|
||||
|
||||
for (const task of data) {
|
||||
// For tasks with subtasks, get the complete ratio from the database function
|
||||
if (task.sub_tasks_count > 0) {
|
||||
try {
|
||||
const result = await db.query("SELECT get_task_complete_ratio($1) AS info;", [task.id]);
|
||||
const [ratioData] = result.rows;
|
||||
if (ratioData && ratioData.info) {
|
||||
task.complete_ratio = +ratioData.info.ratio.toFixed();
|
||||
task.completed_count = ratioData.info.total_completed;
|
||||
task.total_tasks_count = ratioData.info.total_tasks;
|
||||
console.log(`Updated task ${task.id} (${task.name}) from DB: complete_ratio=${task.complete_ratio}`);
|
||||
}
|
||||
} catch (error) {
|
||||
// Proceed with default calculation if database call fails
|
||||
}
|
||||
}
|
||||
|
||||
TasksControllerV2.updateTaskViewModel(task);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,3 +204,29 @@ export async function logPhaseChange(activityLog: IActivityLog) {
|
||||
insertToActivityLogs(activityLog);
|
||||
}
|
||||
}
|
||||
|
||||
export async function logProgressChange(activityLog: IActivityLog) {
|
||||
const { task_id, new_value, old_value } = activityLog;
|
||||
if (!task_id || !activityLog.socket) return;
|
||||
|
||||
if (old_value !== new_value) {
|
||||
activityLog.user_id = getLoggedInUserIdFromSocket(activityLog.socket);
|
||||
activityLog.attribute_type = IActivityLogAttributeTypes.PROGRESS;
|
||||
activityLog.log_type = IActivityLogChangeType.UPDATE;
|
||||
|
||||
insertToActivityLogs(activityLog);
|
||||
}
|
||||
}
|
||||
|
||||
export async function logWeightChange(activityLog: IActivityLog) {
|
||||
const { task_id, new_value, old_value } = activityLog;
|
||||
if (!task_id || !activityLog.socket) return;
|
||||
|
||||
if (old_value !== new_value) {
|
||||
activityLog.user_id = getLoggedInUserIdFromSocket(activityLog.socket);
|
||||
activityLog.attribute_type = IActivityLogAttributeTypes.WEIGHT;
|
||||
activityLog.log_type = IActivityLogChangeType.UPDATE;
|
||||
|
||||
insertToActivityLogs(activityLog);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,8 @@ export enum IActivityLogAttributeTypes {
|
||||
COMMENT = "comment",
|
||||
ARCHIVE = "archive",
|
||||
PHASE = "phase",
|
||||
PROGRESS = "progress",
|
||||
WEIGHT = "weight",
|
||||
}
|
||||
|
||||
export enum IActivityLogChangeType {
|
||||
|
||||
@@ -5,6 +5,8 @@ import TasksControllerV2 from "../../controllers/tasks-controller-v2";
|
||||
|
||||
export async function on_get_task_progress(_io: Server, socket: Socket, taskId?: string) {
|
||||
try {
|
||||
console.log(`GET_TASK_PROGRESS requested for task: ${taskId}`);
|
||||
|
||||
const task: any = {};
|
||||
task.id = taskId;
|
||||
|
||||
@@ -13,6 +15,8 @@ export async function on_get_task_progress(_io: Server, socket: Socket, taskId?:
|
||||
task.complete_ratio = info.ratio;
|
||||
task.completed_count = info.total_completed;
|
||||
task.total_tasks_count = info.total_tasks;
|
||||
|
||||
console.log(`Sending task progress for task ${taskId}: complete_ratio=${task.complete_ratio}`);
|
||||
}
|
||||
|
||||
return socket.emit(SocketEvents.GET_TASK_PROGRESS.toString(), task);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Socket } from "socket.io";
|
||||
import db from "../../config/db";
|
||||
import { SocketEvents } from "../events";
|
||||
import { log, log_error, notifyProjectUpdates } from "../util";
|
||||
import { logProgressChange } from "../../services/activity-logs/activity-logs.service";
|
||||
|
||||
interface UpdateTaskProgressData {
|
||||
task_id: string;
|
||||
@@ -16,10 +17,38 @@ export async function on_update_task_progress(io: any, socket: Socket, data: str
|
||||
const parsedData = JSON.parse(data) as UpdateTaskProgressData;
|
||||
const { task_id, progress_value, parent_task_id } = parsedData;
|
||||
|
||||
console.log(`Updating progress for task ${task_id}: new value = ${progress_value}`);
|
||||
|
||||
if (!task_id || progress_value === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if this is a parent task (has subtasks)
|
||||
const subTasksResult = await db.query(
|
||||
"SELECT COUNT(*) as subtask_count FROM tasks WHERE parent_task_id = $1",
|
||||
[task_id]
|
||||
);
|
||||
|
||||
const subtaskCount = parseInt(subTasksResult.rows[0]?.subtask_count || '0');
|
||||
|
||||
// If this is a parent task, we shouldn't set manual progress
|
||||
if (subtaskCount > 0) {
|
||||
console.log(`Cannot set manual progress on parent task ${task_id} with ${subtaskCount} subtasks`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the current progress value to log the change
|
||||
const currentProgressResult = await db.query(
|
||||
"SELECT progress_value, project_id, team_id FROM tasks WHERE id = $1",
|
||||
[task_id]
|
||||
);
|
||||
|
||||
const currentProgress = currentProgressResult.rows[0]?.progress_value;
|
||||
const projectId = currentProgressResult.rows[0]?.project_id;
|
||||
const teamId = currentProgressResult.rows[0]?.team_id;
|
||||
|
||||
console.log(`Previous progress for task ${task_id}: ${currentProgress}; New: ${progress_value}`);
|
||||
|
||||
// Update the task progress in the database
|
||||
await db.query(
|
||||
`UPDATE tasks
|
||||
@@ -28,9 +57,13 @@ export async function on_update_task_progress(io: any, socket: Socket, data: str
|
||||
[progress_value, task_id]
|
||||
);
|
||||
|
||||
// Get the project ID for the task
|
||||
const projectResult = await db.query("SELECT project_id FROM tasks WHERE id = $1", [task_id]);
|
||||
const projectId = projectResult.rows[0]?.project_id;
|
||||
// Log the progress change using the activity logs service
|
||||
await logProgressChange({
|
||||
task_id,
|
||||
old_value: currentProgress !== null ? currentProgress.toString() : '0',
|
||||
new_value: progress_value.toString(),
|
||||
socket
|
||||
});
|
||||
|
||||
if (projectId) {
|
||||
// Emit the update to all clients in the project room
|
||||
@@ -42,6 +75,8 @@ export async function on_update_task_progress(io: any, socket: Socket, data: str
|
||||
}
|
||||
);
|
||||
|
||||
console.log(`Emitted progress update for task ${task_id} to project room ${projectId}`);
|
||||
|
||||
// If this is a subtask, update the parent task's progress
|
||||
if (parent_task_id) {
|
||||
const progressRatio = await db.query(
|
||||
@@ -49,6 +84,8 @@ export async function on_update_task_progress(io: any, socket: Socket, data: str
|
||||
[parent_task_id]
|
||||
);
|
||||
|
||||
console.log(`Updated parent task ${parent_task_id} progress: ${progressRatio?.rows[0]?.ratio}`);
|
||||
|
||||
// Emit the parent task's updated progress
|
||||
io.to(projectId).emit(
|
||||
SocketEvents.TASK_PROGRESS_UPDATED.toString(),
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Socket } from "socket.io";
|
||||
import db from "../../config/db";
|
||||
import { SocketEvents } from "../events";
|
||||
import { log, log_error, notifyProjectUpdates } from "../util";
|
||||
import { logWeightChange } from "../../services/activity-logs/activity-logs.service";
|
||||
|
||||
interface UpdateTaskWeightData {
|
||||
task_id: string;
|
||||
@@ -11,7 +12,6 @@ interface UpdateTaskWeightData {
|
||||
|
||||
export async function on_update_task_weight(io: any, socket: Socket, data: string) {
|
||||
try {
|
||||
log(socket.id, `${SocketEvents.UPDATE_TASK_WEIGHT}: ${data}`);
|
||||
|
||||
const parsedData = JSON.parse(data) as UpdateTaskWeightData;
|
||||
const { task_id, weight, parent_task_id } = parsedData;
|
||||
@@ -20,6 +20,15 @@ export async function on_update_task_weight(io: any, socket: Socket, data: strin
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the current weight value to log the change
|
||||
const currentWeightResult = await db.query(
|
||||
"SELECT weight, project_id FROM tasks WHERE id = $1",
|
||||
[task_id]
|
||||
);
|
||||
|
||||
const currentWeight = currentWeightResult.rows[0]?.weight;
|
||||
const projectId = currentWeightResult.rows[0]?.project_id;
|
||||
|
||||
// Update the task weight in the database
|
||||
await db.query(
|
||||
`UPDATE tasks
|
||||
@@ -28,9 +37,13 @@ export async function on_update_task_weight(io: any, socket: Socket, data: strin
|
||||
[weight, task_id]
|
||||
);
|
||||
|
||||
// Get the project ID for the task
|
||||
const projectResult = await db.query("SELECT project_id FROM tasks WHERE id = $1", [task_id]);
|
||||
const projectId = projectResult.rows[0]?.project_id;
|
||||
// Log the weight change using the activity logs service
|
||||
await logWeightChange({
|
||||
task_id,
|
||||
old_value: currentWeight !== null ? currentWeight.toString() : '100',
|
||||
new_value: weight.toString(),
|
||||
socket
|
||||
});
|
||||
|
||||
if (projectId) {
|
||||
// Emit the update to all clients in the project room
|
||||
|
||||
Reference in New Issue
Block a user