625 lines
34 KiB
TypeScript
625 lines
34 KiB
TypeScript
import WorklenzControllerBase from "../worklenz-controller-base";
|
|
import { IWorkLenzRequest } from "../../interfaces/worklenz-request";
|
|
import db from "../../config/db";
|
|
import moment from "moment";
|
|
import { DATE_RANGES, TASK_PRIORITY_COLOR_ALPHA } from "../../shared/constants";
|
|
import { formatDuration, formatLogText, getColor, int } from "../../shared/utils";
|
|
|
|
export default abstract class ReportingControllerBase extends WorklenzControllerBase {
|
|
protected static getPercentage(n: number, total: number) {
|
|
return +(n ? (n / total) * 100 : 0).toFixed();
|
|
}
|
|
|
|
protected static getCurrentTeamId(req: IWorkLenzRequest): string | null {
|
|
return req.user?.team_id ?? null;
|
|
}
|
|
|
|
protected static async getTotalTasksCount(projectId: string | null) {
|
|
const q = `
|
|
SELECT COUNT(*) AS count
|
|
FROM tasks
|
|
WHERE project_id = $1;
|
|
`;
|
|
const result = await db.query(q, [projectId]);
|
|
const [data] = result.rows;
|
|
return data.count || 0;
|
|
}
|
|
|
|
protected static async getArchivedProjectsClause(archived = false, user_id: string, column_name: string) {
|
|
return archived
|
|
? ""
|
|
: `AND ${column_name} NOT IN (SELECT project_id FROM archived_projects WHERE project_id = ${column_name} AND user_id = '${user_id}') `;
|
|
}
|
|
|
|
protected static async getAllTasks(projectId: string | null) {
|
|
const q = `
|
|
SELECT id,
|
|
name,
|
|
parent_task_id,
|
|
parent_task_id IS NOT NULL AS is_sub_task,
|
|
status_id AS status,
|
|
(SELECT name FROM task_statuses WHERE id = tasks.status_id) AS status_name,
|
|
(SELECT color_code
|
|
FROM sys_task_status_categories
|
|
WHERE id = (SELECT category_id FROM task_statuses WHERE id = status_id)) AS status_color,
|
|
priority_id AS priority,
|
|
(SELECT value FROM task_priorities WHERE id = tasks.priority_id) AS priority_value,
|
|
(SELECT name FROM task_priorities WHERE id = tasks.priority_id) AS priority_name,
|
|
(SELECT color_code FROM task_priorities WHERE id = tasks.priority_id) AS priority_color,
|
|
end_date,
|
|
(SELECT phase_id FROM task_phase WHERE task_id = tasks.id) AS phase_id,
|
|
(SELECT name
|
|
FROM project_phases
|
|
WHERE id = (SELECT phase_id FROM task_phase WHERE task_id = tasks.id)) AS phase_name,
|
|
completed_at,
|
|
total_minutes,
|
|
(SELECT SUM(time_spent) FROM task_work_log WHERE task_id = tasks.id) AS total_seconds_spent
|
|
FROM tasks
|
|
WHERE project_id = $1
|
|
ORDER BY name;
|
|
`;
|
|
const result = await db.query(q, [projectId]);
|
|
|
|
for (const item of result.rows) {
|
|
const endDate = moment(item.end_date);
|
|
const completedDate = moment(item.completed_at);
|
|
const overdueDays = completedDate.diff(endDate, "days");
|
|
|
|
if (overdueDays > 0) {
|
|
item.overdue_days = overdueDays.toString();
|
|
} else {
|
|
item.overdue_days = "0";
|
|
}
|
|
|
|
item.total_minutes_spent = Math.ceil(item.total_seconds_spent / 60);
|
|
|
|
if (~~(item.total_minutes_spent) > ~~(item.total_minutes)) {
|
|
const overlogged_time = ~~(item.total_minutes_spent) - ~~(item.total_minutes);
|
|
item.overlogged_time_string = formatDuration(moment.duration(overlogged_time, "minutes"));
|
|
} else {
|
|
item.overlogged_time_string = `0h 0m`;
|
|
}
|
|
}
|
|
|
|
return result.rows;
|
|
}
|
|
|
|
protected static getDateRangeClause(key: string, dateRange: string[]) {
|
|
if (dateRange.length === 2) {
|
|
const start = moment(dateRange[0]).format("YYYY-MM-DD");
|
|
const end = moment(dateRange[1]).format("YYYY-MM-DD");
|
|
let query = `AND task_work_log.created_at::DATE >= '${start}'::DATE AND task_work_log.created_at < '${end}'::DATE + INTERVAL '1 day'`;
|
|
|
|
if (start === end) {
|
|
query = `AND task_work_log.created_at::DATE = '${start}'::DATE`;
|
|
}
|
|
|
|
return query;
|
|
}
|
|
|
|
if (key === DATE_RANGES.YESTERDAY)
|
|
return "AND task_work_log.created_at >= (CURRENT_DATE - INTERVAL '1 day')::DATE AND task_work_log.created_at < CURRENT_DATE::DATE";
|
|
if (key === DATE_RANGES.LAST_WEEK)
|
|
return "AND task_work_log.created_at >= (CURRENT_DATE - INTERVAL '1 week')::DATE AND task_work_log.created_at < CURRENT_DATE::DATE + INTERVAL '1 day'";
|
|
if (key === DATE_RANGES.LAST_MONTH)
|
|
return "AND task_work_log.created_at >= (CURRENT_DATE - INTERVAL '1 month')::DATE AND task_work_log.created_at < CURRENT_DATE::DATE + INTERVAL '1 day'";
|
|
if (key === DATE_RANGES.LAST_QUARTER)
|
|
return "AND task_work_log.created_at >= (CURRENT_DATE - INTERVAL '3 months')::DATE AND task_work_log.created_at < CURRENT_DATE::DATE + INTERVAL '1 day'";
|
|
|
|
return "";
|
|
}
|
|
|
|
protected static buildBillableQuery(selectedStatuses: { billable: boolean; nonBillable: boolean }): string {
|
|
const { billable, nonBillable } = selectedStatuses;
|
|
|
|
if (billable && nonBillable) {
|
|
// Both are enabled, no need to filter
|
|
return "";
|
|
} else if (billable) {
|
|
// Only billable is enabled
|
|
return " AND tasks.billable IS TRUE";
|
|
} else if (nonBillable) {
|
|
// Only non-billable is enabled
|
|
return " AND tasks.billable IS FALSE";
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
protected static formatEndDate(endDate: string) {
|
|
const end = moment(endDate).format("YYYY-MM-DD");
|
|
const fEndDate = moment(end);
|
|
return fEndDate;
|
|
}
|
|
|
|
protected static formatCurrentDate() {
|
|
const current = moment().format("YYYY-MM-DD");
|
|
const fCurrentDate = moment(current);
|
|
return fCurrentDate;
|
|
}
|
|
|
|
protected static getDaysLeft(endDate: string): number | null {
|
|
if (!endDate) return null;
|
|
|
|
const fCurrentDate = this.formatCurrentDate();
|
|
const fEndDate = this.formatEndDate(endDate);
|
|
|
|
return fEndDate.diff(fCurrentDate, "days");
|
|
}
|
|
|
|
protected static isOverdue(endDate: string): boolean {
|
|
if (!endDate) return false;
|
|
|
|
const fCurrentDate = this.formatCurrentDate();
|
|
const fEndDate = this.formatEndDate(endDate);
|
|
|
|
return fEndDate.isBefore(fCurrentDate);
|
|
}
|
|
|
|
protected static isToday(endDate: string): boolean {
|
|
if (!endDate) return false;
|
|
|
|
const fCurrentDate = this.formatCurrentDate();
|
|
const fEndDate = this.formatEndDate(endDate);
|
|
|
|
return fEndDate.isSame(fCurrentDate);
|
|
}
|
|
|
|
|
|
public static async getProjectsByTeam(
|
|
teamId: string,
|
|
size: string | number | null,
|
|
offset: string | number | null,
|
|
searchQuery: string | null,
|
|
sortField: string,
|
|
sortOrder: string,
|
|
statusClause: string,
|
|
healthClause: string,
|
|
categoryClause: string,
|
|
archivedClause = "",
|
|
teamFilterClause: string,
|
|
projectManagersClause: string) {
|
|
|
|
const q = `SELECT COUNT(*) AS total,
|
|
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(t))), '[]'::JSON)
|
|
FROM (SELECT p.id,
|
|
p.name,
|
|
p.color_code,
|
|
|
|
p.health_id AS project_health,
|
|
(SELECT color_code
|
|
FROM sys_project_healths
|
|
WHERE sys_project_healths.id = p.health_id) AS health_color,
|
|
(SELECT name
|
|
FROM sys_project_healths
|
|
WHERE sys_project_healths.id = p.health_id) AS health_name,
|
|
|
|
pc.id AS category_id,
|
|
pc.name AS category_name,
|
|
pc.color_code AS category_color,
|
|
|
|
(SELECT name FROM clients WHERE id = p.client_id) AS client,
|
|
|
|
p.team_id,
|
|
(SELECT name FROM teams WHERE id = p.team_id) AS team_name,
|
|
|
|
ps.id AS status_id,
|
|
ps.name AS status_name,
|
|
ps.color_code AS status_color,
|
|
ps.icon AS status_icon,
|
|
|
|
start_date,
|
|
end_date,
|
|
|
|
(SELECT COALESCE(ROW_TO_JSON(pm), '{}'::JSON)
|
|
FROM (SELECT team_member_id AS id,
|
|
(SELECT COALESCE(ROW_TO_JSON(pmi), '{}'::JSON)
|
|
FROM (SELECT name,
|
|
email,
|
|
avatar_url
|
|
FROM team_member_info_view tmiv
|
|
WHERE tmiv.team_member_id = pm.team_member_id
|
|
AND tmiv.team_id = (SELECT team_id FROM projects WHERE id = p.id)) pmi) AS project_manager_info,
|
|
EXISTS(SELECT email
|
|
FROM email_invitations
|
|
WHERE team_member_id = pm.team_member_id
|
|
AND email_invitations.team_id = (SELECT team_id
|
|
FROM team_member_info_view
|
|
WHERE team_member_id = pm.team_member_id)) AS pending_invitation,
|
|
(SELECT active FROM team_members WHERE id = pm.team_member_id)
|
|
FROM project_members pm
|
|
WHERE project_id =p.id
|
|
AND project_access_level_id = (SELECT id FROM project_access_levels WHERE key = 'PROJECT_MANAGER')) pm) AS project_manager,
|
|
|
|
(SELECT COALESCE(SUM(total_minutes), 0)
|
|
FROM tasks
|
|
WHERE project_id = p.id) AS estimated_time,
|
|
|
|
(SELECT SUM((SELECT COALESCE(SUM(time_spent), 0)
|
|
FROM task_work_log
|
|
WHERE task_id = tasks.id))
|
|
FROM tasks
|
|
WHERE project_id = p.id) AS actual_time,
|
|
|
|
(SELECT ROW_TO_JSON(rec)
|
|
FROM (SELECT COUNT(ta.id) AS total,
|
|
COUNT(CASE WHEN is_completed(ta.status_id, ta.project_id) IS TRUE THEN 1 END) AS done,
|
|
COUNT(CASE WHEN is_doing(ta.status_id, ta.project_id) IS TRUE THEN 1 END) AS doing,
|
|
COUNT(CASE WHEN is_todo(ta.status_id, ta.project_id) IS TRUE THEN 1 END) AS todo
|
|
FROM tasks ta
|
|
WHERE project_id = p.id) rec) AS tasks_stat,
|
|
|
|
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
|
|
FROM (SELECT pu.content AS content,
|
|
(SELECT COALESCE(JSON_AGG(rec), '[]'::JSON)
|
|
FROM (SELECT u.name AS user_name,
|
|
u.email AS user_email
|
|
FROM project_comment_mentions pcm
|
|
LEFT JOIN users u ON pcm.informed_by = u.id
|
|
WHERE pcm.comment_id = pu.id) rec) AS mentions,
|
|
pu.updated_at
|
|
FROM project_comments pu
|
|
WHERE pu.project_id = p.id
|
|
ORDER BY pu.updated_at DESC
|
|
LIMIT 1) AS rec) AS update,
|
|
|
|
(SELECT ROW_TO_JSON(rec)
|
|
FROM (SELECT attribute_type,
|
|
log_type,
|
|
-- new case,
|
|
(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)
|
|
ELSE (old_value) END) AS previous,
|
|
|
|
-- new case
|
|
(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)
|
|
ELSE (new_value) END) AS current,
|
|
(SELECT name
|
|
FROM users
|
|
WHERE id = (SELECT reporter_id FROM tasks WHERE id = tal.task_id)),
|
|
(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,
|
|
(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,
|
|
(SELECT name FROM tasks WHERE tasks.id = tal.task_id)
|
|
FROM task_activity_logs tal
|
|
WHERE task_id IN (SELECT id FROM tasks t WHERE t.project_id = p.id)
|
|
ORDER BY tal.created_at DESC
|
|
LIMIT 1) rec) AS last_activity
|
|
FROM projects p
|
|
LEFT JOIN project_categories pc ON pc.id = p.category_id
|
|
LEFT JOIN sys_project_statuses ps ON p.status_id = ps.id
|
|
WHERE ${teamFilterClause} ${searchQuery} ${healthClause} ${statusClause} ${categoryClause} ${projectManagersClause} ${archivedClause}
|
|
ORDER BY ${sortField} ${sortOrder}
|
|
LIMIT $2 OFFSET $3) t) AS projects
|
|
FROM projects p
|
|
LEFT JOIN project_categories pc ON pc.id = p.category_id
|
|
LEFT JOIN sys_project_statuses ps ON p.status_id = ps.id
|
|
WHERE ${teamFilterClause} ${searchQuery} ${healthClause} ${statusClause} ${categoryClause} ${projectManagersClause} ${archivedClause};`;
|
|
const result = await db.query(q, [teamId, size, offset]);
|
|
const [data] = result.rows;
|
|
|
|
for (const project of data.projects) {
|
|
if (project.project_manager) {
|
|
project.project_manager.name = project.project_manager.project_manager_info.name;
|
|
project.project_manager.avatar_url = project.project_manager.project_manager_info.avatar_url;
|
|
project.project_manager.color_code = getColor(project.project_manager.name);
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
public static convertMinutesToHoursAndMinutes(totalMinutes: number) {
|
|
const hours = Math.floor(totalMinutes / 60);
|
|
const minutes = totalMinutes % 60;
|
|
return `${hours}h ${minutes}m`;
|
|
}
|
|
|
|
public static convertSecondsToHoursAndMinutes(seconds: number) {
|
|
const hours = Math.floor(seconds / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
return `${hours}h ${minutes}m`;
|
|
}
|
|
|
|
public static async exportProjects(teamId: string) {
|
|
const q = `SELECT COUNT(*) AS total,
|
|
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(t))), '[]'::JSON)
|
|
FROM (SELECT p.id,
|
|
p.name,
|
|
(SELECT name
|
|
FROM sys_project_healths
|
|
WHERE sys_project_healths.id = p.health_id) AS project_health,
|
|
pc.name AS category_name,
|
|
(SELECT name FROM clients WHERE id = p.client_id) AS client,
|
|
(SELECT name FROM teams WHERE id = p.team_id) AS team_name,
|
|
ps.name AS status_name,
|
|
start_date,
|
|
end_date,
|
|
(SELECT COALESCE(SUM(total_minutes), 0)
|
|
FROM tasks
|
|
WHERE project_id = p.id) AS estimated_time,
|
|
(SELECT SUM((SELECT COALESCE(SUM(time_spent), 0)
|
|
FROM task_work_log
|
|
WHERE task_id = tasks.id))
|
|
FROM tasks
|
|
WHERE project_id = p.id) AS actual_time,
|
|
(SELECT ROW_TO_JSON(rec)
|
|
FROM (SELECT COUNT(ta.id) AS total,
|
|
COUNT(CASE WHEN is_completed(ta.status_id, ta.project_id) IS TRUE THEN 1 END) AS done,
|
|
COUNT(CASE WHEN is_doing(ta.status_id, ta.project_id) IS TRUE THEN 1 END) AS doing,
|
|
COUNT(CASE WHEN is_todo(ta.status_id, ta.project_id) IS TRUE THEN 1 END) AS todo
|
|
FROM tasks ta
|
|
WHERE project_id = p.id) rec) AS tasks_stat,
|
|
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
|
|
FROM (SELECT pu.content AS content,
|
|
(SELECT COALESCE(JSON_AGG(rec), '[]'::JSON)
|
|
FROM (SELECT u.name AS user_name,
|
|
u.email AS user_email
|
|
FROM project_comment_mentions pcm
|
|
LEFT JOIN users u ON pcm.informed_by = u.id
|
|
WHERE pcm.comment_id = pu.id) rec) AS mentions,
|
|
pu.updated_at
|
|
FROM project_comments pu
|
|
WHERE pu.project_id = p.id
|
|
ORDER BY pu.updated_at DESC
|
|
LIMIT 1) AS rec) AS update,
|
|
(SELECT ROW_TO_JSON(rec)
|
|
FROM (SELECT attribute_type,
|
|
log_type,
|
|
-- new case,
|
|
(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)
|
|
ELSE (old_value) END) AS previous,
|
|
|
|
-- new case
|
|
(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)
|
|
ELSE (new_value) END) AS current,
|
|
(SELECT name
|
|
FROM users
|
|
WHERE id = (SELECT reporter_id FROM tasks WHERE id = tal.task_id)),
|
|
(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,
|
|
(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,
|
|
(SELECT name FROM tasks WHERE tasks.id = tal.task_id)
|
|
FROM task_activity_logs tal
|
|
WHERE task_id IN (SELECT id FROM tasks t WHERE t.project_id = p.id)
|
|
ORDER BY tal.created_at
|
|
LIMIT 1) rec) AS last_activity
|
|
FROM projects p
|
|
LEFT JOIN project_categories pc ON pc.id = p.category_id
|
|
LEFT JOIN sys_project_statuses ps ON p.status_id = ps.id
|
|
WHERE p.team_id = $1 ORDER BY p.name) t) AS projects
|
|
FROM projects p
|
|
LEFT JOIN project_categories pc ON pc.id = p.category_id
|
|
LEFT JOIN sys_project_statuses ps ON p.status_id = ps.id
|
|
WHERE p.team_id = $1;`;
|
|
|
|
const result = await db.query(q, [teamId]);
|
|
|
|
const [data] = result.rows;
|
|
|
|
for (const project of data.projects) {
|
|
project.team_color = getColor(project.team_name) + TASK_PRIORITY_COLOR_ALPHA;
|
|
project.days_left = this.getDaysLeft(project.end_date);
|
|
project.is_overdue = this.isOverdue(project.end_date);
|
|
if (project.days_left && project.is_overdue) {
|
|
project.days_left = project.days_left.toString().replace(/-/g, "");
|
|
}
|
|
project.is_today = this.isToday(project.end_date);
|
|
project.estimated_time = this.convertMinutesToHoursAndMinutes(int(project.estimated_time));
|
|
project.actual_time = this.convertSecondsToHoursAndMinutes(int(project.actual_time));
|
|
project.tasks_stat = {
|
|
todo: this.getPercentage(int(project.tasks_stat.todo), +project.tasks_stat.total),
|
|
doing: this.getPercentage(int(project.tasks_stat.doing), +project.tasks_stat.total),
|
|
done: this.getPercentage(int(project.tasks_stat.done), +project.tasks_stat.total)
|
|
};
|
|
if (project.update.length > 0) {
|
|
const update = project.update[0];
|
|
const placeHolders = update.content.match(/{\d+}/g);
|
|
if (placeHolders) {
|
|
placeHolders.forEach((placeHolder: { match: (arg0: RegExp) => string[]; }) => {
|
|
const index = parseInt(placeHolder.match(/\d+/)[0]);
|
|
if (index >= 0 && index < update.mentions.length) {
|
|
update.content = update.content.replace(placeHolder, ` @${update.mentions[index].user_name} `);
|
|
}
|
|
});
|
|
}
|
|
project.comment = update.content;
|
|
}
|
|
if (project.last_activity) {
|
|
if (project.last_activity.attribute_type === "estimation") {
|
|
project.last_activity.previous = formatDuration(moment.duration(project.last_activity.previous, "minutes"));
|
|
project.last_activity.current = formatDuration(moment.duration(project.last_activity.current, "minutes"));
|
|
}
|
|
if (project.last_activity.assigned_user) project.last_activity.assigned_user.color_code = getColor(project.last_activity.assigned_user.name);
|
|
project.last_activity.done_by.color_code = getColor(project.last_activity.done_by.name);
|
|
project.last_activity.log_text = await formatLogText(project.last_activity);
|
|
project.last_activity.attribute_type = project.last_activity.attribute_type?.replace(/_/g, " ");
|
|
project.last_activity.last_activity_string = `${project.last_activity.done_by.name} ${project.last_activity.log_text} ${project.last_activity.attribute_type}`;
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
public static async exportProjectsAll(teamId: string) {
|
|
const q = `SELECT COUNT(*) AS total,
|
|
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(t))), '[]'::JSON)
|
|
FROM (SELECT p.id,
|
|
p.name,
|
|
(SELECT name
|
|
FROM sys_project_healths
|
|
WHERE sys_project_healths.id = p.health_id) AS project_health,
|
|
pc.name AS category_name,
|
|
(SELECT name FROM clients WHERE id = p.client_id) AS client,
|
|
(SELECT name FROM teams WHERE id = p.team_id) AS team_name,
|
|
ps.name AS status_name,
|
|
start_date,
|
|
end_date,
|
|
(SELECT COALESCE(SUM(total_minutes), 0)
|
|
FROM tasks
|
|
WHERE project_id = p.id) AS estimated_time,
|
|
(SELECT SUM((SELECT COALESCE(SUM(time_spent), 0)
|
|
FROM task_work_log
|
|
WHERE task_id = tasks.id))
|
|
FROM tasks
|
|
WHERE project_id = p.id) AS actual_time,
|
|
(SELECT ROW_TO_JSON(rec)
|
|
FROM (SELECT COUNT(ta.id) AS total,
|
|
COUNT(CASE WHEN is_completed(ta.status_id, ta.project_id) IS TRUE THEN 1 END) AS done,
|
|
COUNT(CASE WHEN is_doing(ta.status_id, ta.project_id) IS TRUE THEN 1 END) AS doing,
|
|
COUNT(CASE WHEN is_todo(ta.status_id, ta.project_id) IS TRUE THEN 1 END) AS todo
|
|
FROM tasks ta
|
|
WHERE project_id = p.id) rec) AS tasks_stat,
|
|
(SELECT COALESCE(ARRAY_TO_JSON(ARRAY_AGG(ROW_TO_JSON(rec))), '[]'::JSON)
|
|
FROM (SELECT pu.content AS content,
|
|
(SELECT COALESCE(JSON_AGG(rec), '[]'::JSON)
|
|
FROM (SELECT u.name AS user_name,
|
|
u.email AS user_email
|
|
FROM project_comment_mentions pcm
|
|
LEFT JOIN users u ON pcm.informed_by = u.id
|
|
WHERE pcm.comment_id = pu.id) rec) AS mentions,
|
|
pu.updated_at
|
|
FROM project_comments pu
|
|
WHERE pu.project_id = p.id
|
|
ORDER BY pu.updated_at DESC
|
|
LIMIT 1) AS rec) AS update,
|
|
(SELECT ROW_TO_JSON(rec)
|
|
FROM (SELECT attribute_type,
|
|
log_type,
|
|
-- new case,
|
|
(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)
|
|
ELSE (old_value) END) AS previous,
|
|
|
|
-- new case
|
|
(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)
|
|
ELSE (new_value) END) AS current,
|
|
(SELECT name
|
|
FROM users
|
|
WHERE id = (SELECT reporter_id FROM tasks WHERE id = tal.task_id)),
|
|
(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,
|
|
(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,
|
|
(SELECT name FROM tasks WHERE tasks.id = tal.task_id)
|
|
FROM task_activity_logs tal
|
|
WHERE task_id IN (SELECT id FROM tasks t WHERE t.project_id = p.id)
|
|
ORDER BY tal.created_at
|
|
LIMIT 1) rec) AS last_activity
|
|
FROM projects p
|
|
LEFT JOIN project_categories pc ON pc.id = p.category_id
|
|
LEFT JOIN sys_project_statuses ps ON p.status_id = ps.id
|
|
WHERE in_organization(p.team_id, $1) ORDER BY p.name) t) AS projects
|
|
FROM projects p
|
|
LEFT JOIN project_categories pc ON pc.id = p.category_id
|
|
LEFT JOIN sys_project_statuses ps ON p.status_id = ps.id
|
|
WHERE in_organization(p.team_id, $1);`;
|
|
|
|
const result = await db.query(q, [teamId]);
|
|
|
|
const [data] = result.rows;
|
|
|
|
for (const project of data.projects) {
|
|
project.team_color = getColor(project.team_name) + TASK_PRIORITY_COLOR_ALPHA;
|
|
project.days_left = this.getDaysLeft(project.end_date);
|
|
project.is_overdue = this.isOverdue(project.end_date);
|
|
if (project.days_left && project.is_overdue) {
|
|
project.days_left = project.days_left.toString().replace(/-/g, "");
|
|
}
|
|
project.is_today = this.isToday(project.end_date);
|
|
project.estimated_time = this.convertMinutesToHoursAndMinutes(int(project.estimated_time));
|
|
project.actual_time = this.convertSecondsToHoursAndMinutes(int(project.actual_time));
|
|
project.tasks_stat = {
|
|
todo: this.getPercentage(int(project.tasks_stat.todo), +project.tasks_stat.total),
|
|
doing: this.getPercentage(int(project.tasks_stat.doing), +project.tasks_stat.total),
|
|
done: this.getPercentage(int(project.tasks_stat.done), +project.tasks_stat.total)
|
|
};
|
|
if (project.update.length > 0) {
|
|
const update = project.update[0];
|
|
const placeHolders = update.content.match(/{\d+}/g);
|
|
if (placeHolders) {
|
|
placeHolders.forEach((placeHolder: { match: (arg0: RegExp) => string[]; }) => {
|
|
const index = parseInt(placeHolder.match(/\d+/)[0]);
|
|
if (index >= 0 && index < update.mentions.length) {
|
|
update.content = update.content.replace(placeHolder, ` @${update.mentions[index].user_name} `);
|
|
}
|
|
});
|
|
}
|
|
project.comment = update.content;
|
|
}
|
|
if (project.last_activity) {
|
|
if (project.last_activity.attribute_type === "estimation") {
|
|
project.last_activity.previous = formatDuration(moment.duration(project.last_activity.previous, "minutes"));
|
|
project.last_activity.current = formatDuration(moment.duration(project.last_activity.current, "minutes"));
|
|
}
|
|
if (project.last_activity.assigned_user) project.last_activity.assigned_user.color_code = getColor(project.last_activity.assigned_user.name);
|
|
project.last_activity.done_by.color_code = getColor(project.last_activity.done_by.name);
|
|
project.last_activity.log_text = await formatLogText(project.last_activity);
|
|
project.last_activity.attribute_type = project.last_activity.attribute_type?.replace(/_/g, " ");
|
|
project.last_activity.last_activity_string = `${project.last_activity.done_by.name} ${project.last_activity.log_text} ${project.last_activity.attribute_type}`;
|
|
}
|
|
}
|
|
return data;
|
|
}
|
|
|
|
}
|