mirror of
https://github.com/makeplane/plane.git
synced 2025-12-25 16:19:43 +01:00
358 lines
9.9 KiB
TypeScript
358 lines
9.9 KiB
TypeScript
import { ARRAY_FIELDS, GROUP_BY_MAP, PRIORITY_MAP } from "./constants";
|
|
import { SPECIAL_ORDER_BY } from "./query-constructor";
|
|
import { issueSchema } from "./schemas";
|
|
import { wrapDateTime } from "./utils";
|
|
|
|
export const translateQueryParams = (queries: any) => {
|
|
const {
|
|
group_by,
|
|
layout,
|
|
sub_group_by,
|
|
labels,
|
|
assignees,
|
|
state,
|
|
cycle,
|
|
module,
|
|
priority,
|
|
type,
|
|
issue_type,
|
|
...otherProps
|
|
} = queries;
|
|
|
|
const order_by = queries.order_by;
|
|
if (state) otherProps.state_id = state;
|
|
if (cycle) otherProps.cycle_id = cycle;
|
|
if (module) otherProps.module_ids = module;
|
|
if (labels) otherProps.label_ids = labels;
|
|
if (assignees) otherProps.assignee_ids = assignees;
|
|
if (group_by) otherProps.group_by = GROUP_BY_MAP[group_by as keyof typeof GROUP_BY_MAP];
|
|
if (sub_group_by) otherProps.sub_group_by = GROUP_BY_MAP[sub_group_by as keyof typeof GROUP_BY_MAP];
|
|
if (priority) {
|
|
otherProps.priority_proxy = priority
|
|
.split(",")
|
|
.map((priority: string) => PRIORITY_MAP[priority as keyof typeof PRIORITY_MAP])
|
|
.join(",");
|
|
}
|
|
if (type) {
|
|
otherProps.state_group = type === "backlog" ? "backlog" : "unstarted,started";
|
|
}
|
|
if (issue_type) {
|
|
otherProps.type_id = issue_type;
|
|
}
|
|
|
|
if (order_by?.includes("priority")) {
|
|
otherProps.order_by = order_by.replace("priority", "priority_proxy");
|
|
}
|
|
|
|
// Fix invalid orderby when switching from spreadsheet layout
|
|
if (layout !== "spreadsheet" && Object.keys(SPECIAL_ORDER_BY).includes(order_by)) {
|
|
otherProps.order_by = "sort_order";
|
|
}
|
|
// For each property value, replace None with empty string
|
|
Object.keys(otherProps).forEach((key) => {
|
|
if (otherProps[key] === "None") {
|
|
otherProps[key] = "";
|
|
}
|
|
});
|
|
|
|
return otherProps;
|
|
};
|
|
|
|
export const getOrderByFragment = (order_by: string, table = "") => {
|
|
let orderByString = "";
|
|
if (!order_by) return orderByString;
|
|
|
|
if (order_by.startsWith("-")) {
|
|
orderByString += ` ORDER BY ${wrapDateTime(order_by.slice(1))} DESC NULLS LAST, ${table}sequence_id DESC`;
|
|
} else {
|
|
orderByString += ` ORDER BY ${wrapDateTime(order_by)} ASC NULLS LAST, ${table}sequence_id DESC`;
|
|
}
|
|
return orderByString;
|
|
};
|
|
|
|
export const isMetaJoinRequired = (groupBy: string, subGroupBy: string) =>
|
|
ARRAY_FIELDS.includes(groupBy) || ARRAY_FIELDS.includes(subGroupBy);
|
|
|
|
export const getMetaKeysFragment = (queries: any) => {
|
|
const { group_by, sub_group_by, ...otherProps } = translateQueryParams(queries);
|
|
|
|
const fields: Set<string> = new Set();
|
|
if (ARRAY_FIELDS.includes(group_by)) {
|
|
fields.add(group_by);
|
|
}
|
|
|
|
if (ARRAY_FIELDS.includes(sub_group_by)) {
|
|
fields.add(sub_group_by);
|
|
}
|
|
|
|
const keys = Object.keys(otherProps);
|
|
|
|
keys.forEach((field: string) => {
|
|
if (ARRAY_FIELDS.includes(field)) {
|
|
fields.add(field);
|
|
}
|
|
});
|
|
|
|
const sql = ` ('${Array.from(fields).join("','")}')`;
|
|
|
|
return sql;
|
|
};
|
|
|
|
export const getMetaKeys = (queries: any): string[] => {
|
|
const { group_by, sub_group_by, ...otherProps } = translateQueryParams(queries);
|
|
|
|
const fields: Set<string> = new Set();
|
|
if (ARRAY_FIELDS.includes(group_by)) {
|
|
fields.add(group_by);
|
|
}
|
|
|
|
if (ARRAY_FIELDS.includes(sub_group_by)) {
|
|
fields.add(sub_group_by);
|
|
}
|
|
|
|
const keys = Object.keys(otherProps);
|
|
|
|
keys.forEach((field: string) => {
|
|
if (ARRAY_FIELDS.includes(field)) {
|
|
fields.add(field);
|
|
}
|
|
});
|
|
|
|
return Array.from(fields);
|
|
};
|
|
|
|
const areJoinsRequired = (queries: any) => {
|
|
const { group_by, sub_group_by, ...otherProps } = translateQueryParams(queries);
|
|
|
|
if (ARRAY_FIELDS.includes(group_by) || ARRAY_FIELDS.includes(sub_group_by)) {
|
|
return true;
|
|
}
|
|
if (Object.keys(otherProps).some((field) => ARRAY_FIELDS.includes(field))) {
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
// Apply filters to the query
|
|
export const getFilteredRowsForGrouping = (projectId: string, queries: any) => {
|
|
const { group_by, sub_group_by, ...otherProps } = translateQueryParams(queries);
|
|
|
|
const filterJoinFields = getMetaKeys(otherProps);
|
|
|
|
const temp = getSingleFilterFields(queries);
|
|
const issueTableFilterFields = temp.length ? "," + temp.join(",") : "";
|
|
|
|
const joinsRequired = areJoinsRequired(queries);
|
|
|
|
let sql = "";
|
|
if (!joinsRequired) {
|
|
sql = `WITH fi as (SELECT i.id,i.created_at, i.sequence_id ${issueTableFilterFields}`;
|
|
if (group_by) {
|
|
if (group_by === "target_date") {
|
|
sql += `, date(i.${group_by}) as group_id`;
|
|
} else {
|
|
sql += `, i.${group_by} as group_id`;
|
|
}
|
|
}
|
|
if (sub_group_by) {
|
|
sql += `, i.${sub_group_by} as sub_group_id`;
|
|
}
|
|
sql += ` FROM issues i `;
|
|
if (otherProps.state_group) {
|
|
sql += `LEFT JOIN states ON i.state_id = states.id `;
|
|
}
|
|
sql += `WHERE 1=1 `;
|
|
if (projectId) {
|
|
sql += ` AND i.project_id = '${projectId}'
|
|
`;
|
|
}
|
|
sql += `${singleFilterConstructor(otherProps)})
|
|
`;
|
|
return sql;
|
|
}
|
|
|
|
sql = `WITH fi AS (`;
|
|
sql += `SELECT i.id,i.created_at,i.sequence_id ${issueTableFilterFields} `;
|
|
if (group_by) {
|
|
if (ARRAY_FIELDS.includes(group_by)) {
|
|
sql += `, ${group_by}.value as group_id
|
|
`;
|
|
} else if (group_by === "target_date") {
|
|
sql += `, date(i.${group_by}) as group_id
|
|
`;
|
|
} else {
|
|
sql += `, i.${group_by} as group_id
|
|
`;
|
|
}
|
|
}
|
|
if (sub_group_by) {
|
|
if (ARRAY_FIELDS.includes(sub_group_by)) {
|
|
sql += `, ${sub_group_by}.value as sub_group_id
|
|
`;
|
|
} else {
|
|
sql += `, i.${sub_group_by} as sub_group_id
|
|
`;
|
|
}
|
|
}
|
|
|
|
sql += ` from issues i
|
|
`;
|
|
if (otherProps.state_group) {
|
|
sql += `LEFT JOIN states ON i.state_id = states.id `;
|
|
}
|
|
filterJoinFields.forEach((field: string) => {
|
|
sql += ` INNER JOIN issue_meta ${field} ON i.id = ${field}.issue_id AND ${field}.key = '${field}' AND ${field}.value IN ('${otherProps[field].split(",").join("','")}')
|
|
`;
|
|
});
|
|
|
|
// If group by field is not already joined, join it
|
|
if (ARRAY_FIELDS.includes(group_by) && !filterJoinFields.includes(group_by)) {
|
|
sql += ` LEFT JOIN issue_meta ${group_by} ON i.id = ${group_by}.issue_id AND ${group_by}.key = '${group_by}'
|
|
`;
|
|
}
|
|
if (ARRAY_FIELDS.includes(sub_group_by) && !filterJoinFields.includes(sub_group_by)) {
|
|
sql += ` LEFT JOIN issue_meta ${sub_group_by} ON i.id = ${sub_group_by}.issue_id AND ${sub_group_by}.key = '${sub_group_by}'
|
|
`;
|
|
}
|
|
|
|
sql += ` WHERE 1=1 `;
|
|
if (projectId) {
|
|
sql += ` AND i.project_id = '${projectId}'
|
|
`;
|
|
}
|
|
sql += singleFilterConstructor(otherProps);
|
|
|
|
sql += `)
|
|
`;
|
|
return sql;
|
|
};
|
|
|
|
export const singleFilterConstructor = (queries: any) => {
|
|
const {
|
|
order_by,
|
|
cursor,
|
|
per_page,
|
|
group_by,
|
|
sub_group_by,
|
|
state_group,
|
|
sub_issue,
|
|
target_date,
|
|
start_date,
|
|
...filters
|
|
} = translateQueryParams(queries);
|
|
|
|
let sql = "";
|
|
if (!sub_issue) {
|
|
sql += ` AND parent_id IS NULL
|
|
`;
|
|
}
|
|
if (target_date) {
|
|
sql += createDateFilter("target_date", target_date);
|
|
}
|
|
if (start_date) {
|
|
sql += createDateFilter("start_date", start_date);
|
|
}
|
|
if (state_group) {
|
|
sql += ` AND state_group in ('${state_group.split(",").join("','")}')
|
|
`;
|
|
}
|
|
const keys = Object.keys(filters);
|
|
|
|
keys.forEach((key) => {
|
|
const value = filters[key] ? filters[key].split(",") : "";
|
|
if (!ARRAY_FIELDS.includes(key)) {
|
|
if (!value) {
|
|
sql += ` AND ${key} IS NULL`;
|
|
return;
|
|
}
|
|
sql += ` AND ${key} in ('${value.join("','")}')
|
|
`;
|
|
}
|
|
});
|
|
//
|
|
|
|
return sql;
|
|
};
|
|
|
|
const createDateFilter = (key: string, q: string) => {
|
|
let sql = " ";
|
|
// get todays date in YYYY-MM-DD format
|
|
const queries = q.split(",");
|
|
const customRange: string[] = [];
|
|
let isAnd = true;
|
|
queries.forEach((query: string) => {
|
|
const [date, type, from] = query.split(";");
|
|
if (from) {
|
|
// Assuming type is always after
|
|
let after = "";
|
|
const [_length, unit] = date.split("_");
|
|
const length = parseInt(_length);
|
|
|
|
if (unit === "weeks") {
|
|
// get date in yyyy-mm-dd format one week from now
|
|
after = new Date(new Date().setDate(new Date().getDate() + length * 7)).toISOString().split("T")[0];
|
|
}
|
|
if (unit === "months") {
|
|
after = new Date(new Date().setDate(new Date().getDate() + length * 30)).toISOString().split("T")[0];
|
|
}
|
|
sql += ` ${isAnd ? "AND" : "OR"} ${key} >= date('${after}')`;
|
|
isAnd = false;
|
|
// sql += ` AND ${key} ${type === "after" ? ">=" : "<="} date('${date}', '${today}')`;
|
|
} else {
|
|
customRange.push(query);
|
|
}
|
|
});
|
|
|
|
if (customRange.length === 2) {
|
|
const end = customRange.find((date) => date.includes("before"))?.split(";")[0];
|
|
const start = customRange.find((date) => date.includes("after"))?.split(";")[0];
|
|
if (end && start) {
|
|
sql += ` ${isAnd ? "AND" : "OR"} ${key} BETWEEN date('${start}') AND date('${end}')`;
|
|
}
|
|
}
|
|
if (customRange.length === 1) {
|
|
sql += ` AND ${key}=date('${customRange[0].split(";")[0]}')`;
|
|
}
|
|
|
|
return sql;
|
|
};
|
|
const getSingleFilterFields = (queries: any) => {
|
|
const { order_by, cursor, per_page, group_by, sub_group_by, sub_issue, state_group, ...otherProps } =
|
|
translateQueryParams(queries);
|
|
|
|
const fields = new Set();
|
|
|
|
if (order_by && !order_by.includes("created_at") && !Object.keys(SPECIAL_ORDER_BY).includes(order_by))
|
|
fields.add(order_by.replace("-", ""));
|
|
|
|
const keys = Object.keys(otherProps);
|
|
|
|
keys.forEach((field: string) => {
|
|
if (!ARRAY_FIELDS.includes(field)) {
|
|
fields.add(field);
|
|
}
|
|
});
|
|
|
|
if (order_by?.includes("state__name")) {
|
|
fields.add("state_id");
|
|
}
|
|
if (order_by?.includes("cycle__name")) {
|
|
fields.add("cycle_id");
|
|
}
|
|
if (state_group) {
|
|
fields.add("states.'group' as state_group");
|
|
}
|
|
if (order_by?.includes("estimate_point__key")) {
|
|
fields.add("estimate_point");
|
|
}
|
|
return Array.from(fields);
|
|
};
|
|
|
|
export const getIssueFieldsFragment = () => {
|
|
const { description_html, ...filtered } = issueSchema;
|
|
const keys = Object.keys(filtered);
|
|
const sql = ` ${keys.map((key) => `i.${key}`).join(`,
|
|
`)}`;
|
|
return sql;
|
|
};
|