mirror of
https://github.com/makeplane/plane.git
synced 2025-12-16 20:07:56 +01:00
* fix: progress chart code splitting * fix: progress chart code splitting * fix: build errors + review changes
122 lines
4.5 KiB
TypeScript
122 lines
4.5 KiB
TypeScript
import { isEmpty, orderBy, uniqBy } from "lodash";
|
|
import sortBy from "lodash/sortBy";
|
|
import { ICycle, TCycleFilters } from "@plane/types";
|
|
// helpers
|
|
import { generateDateArray, getDate, getToday } from "@/helpers/date-time.helper";
|
|
import { satisfiesDateFilter } from "@/helpers/filter.helper";
|
|
|
|
export type TProgressChartData = {
|
|
date: string;
|
|
scope: number;
|
|
completed: number;
|
|
backlog: number;
|
|
started: number;
|
|
unstarted: number;
|
|
cancelled: number;
|
|
pending: number;
|
|
ideal: number;
|
|
actual: number;
|
|
}[];
|
|
|
|
/**
|
|
* @description orders cycles based on their status
|
|
* @param {ICycle[]} cycles
|
|
* @returns {ICycle[]}
|
|
*/
|
|
export const orderCycles = (cycles: ICycle[], sortByManual: boolean): ICycle[] => {
|
|
if (cycles.length === 0) return [];
|
|
|
|
const acceptedStatuses = ["current", "upcoming", "draft"];
|
|
const STATUS_ORDER: {
|
|
[key: string]: number;
|
|
} = {
|
|
current: 1,
|
|
upcoming: 2,
|
|
draft: 3,
|
|
};
|
|
|
|
let filteredCycles = cycles.filter((c) => acceptedStatuses.includes(c.status?.toLowerCase() ?? ""));
|
|
if (sortByManual) filteredCycles = sortBy(filteredCycles, [(c) => c.sort_order]);
|
|
else
|
|
filteredCycles = sortBy(filteredCycles, [
|
|
(c) => STATUS_ORDER[c.status?.toLowerCase() ?? ""],
|
|
(c) => (c.status?.toLowerCase() === "upcoming" ? c.start_date : c.name.toLowerCase()),
|
|
]);
|
|
|
|
return filteredCycles;
|
|
};
|
|
|
|
/**
|
|
* @description filters cycles based on the filter
|
|
* @param {ICycle} cycle
|
|
* @param {TCycleFilters} filter
|
|
* @returns {boolean}
|
|
*/
|
|
export const shouldFilterCycle = (cycle: ICycle, filter: TCycleFilters): boolean => {
|
|
let fallsInFilters = true;
|
|
Object.keys(filter).forEach((key) => {
|
|
const filterKey = key as keyof TCycleFilters;
|
|
if (filterKey === "status" && filter.status && filter.status.length > 0)
|
|
fallsInFilters = fallsInFilters && filter.status.includes(cycle.status?.toLowerCase() ?? "");
|
|
if (filterKey === "start_date" && filter.start_date && filter.start_date.length > 0) {
|
|
const startDate = getDate(cycle.start_date);
|
|
filter.start_date.forEach((dateFilter) => {
|
|
fallsInFilters = fallsInFilters && !!startDate && satisfiesDateFilter(startDate, dateFilter);
|
|
});
|
|
}
|
|
if (filterKey === "end_date" && filter.end_date && filter.end_date.length > 0) {
|
|
const endDate = getDate(cycle.end_date);
|
|
filter.end_date.forEach((dateFilter) => {
|
|
fallsInFilters = fallsInFilters && !!endDate && satisfiesDateFilter(endDate, dateFilter);
|
|
});
|
|
}
|
|
});
|
|
|
|
return fallsInFilters;
|
|
};
|
|
|
|
export const formatActiveCycle = (args: {
|
|
cycle: ICycle;
|
|
isBurnDown?: boolean | undefined;
|
|
isTypeIssue?: boolean | undefined;
|
|
}) => {
|
|
const { cycle, isBurnDown, isTypeIssue } = args;
|
|
let today = getToday();
|
|
const endDate: Date | string = new Date(cycle.end_date!);
|
|
|
|
const extendedArray = endDate > today ? generateDateArray(today as Date, endDate) : [];
|
|
if (isEmpty(cycle.progress)) return extendedArray;
|
|
today = getToday(true);
|
|
|
|
const scope = (p: any) => (isTypeIssue ? p.total_issues : p.total_estimate_points);
|
|
const ideal = (p: any) =>
|
|
isTypeIssue
|
|
? Math.abs(p.total_issues - p.completed_issues + (Math.random() < 0.5 ? -1 : 1))
|
|
: Math.abs(p.total_estimate_points - p.completed_estimate_points + (Math.random() < 0.5 ? -1 : 1));
|
|
|
|
const scopeToday = scope(cycle?.progress[cycle?.progress.length - 1]);
|
|
const idealToday = ideal(cycle?.progress[cycle?.progress.length - 1]);
|
|
|
|
const progress = [...orderBy(cycle?.progress, "date"), ...extendedArray].map((p) => {
|
|
const pending = isTypeIssue
|
|
? p.total_issues - p.completed_issues - p.cancelled_issues
|
|
: p.total_estimate_points - p.completed_estimate_points - p.cancelled_estimate_points;
|
|
const completed = isTypeIssue ? p.completed_issues : p.completed_estimate_points;
|
|
|
|
return {
|
|
date: p.date,
|
|
scope: p.date! < today ? scope(p) : p.date! < cycle.end_date! ? scopeToday : null,
|
|
completed,
|
|
backlog: isTypeIssue ? p.backlog_issues : p.backlog_estimate_points,
|
|
started: isTypeIssue ? p.started_issues : p.started_estimate_points,
|
|
unstarted: isTypeIssue ? p.unstarted_issues : p.unstarted_estimate_points,
|
|
cancelled: isTypeIssue ? p.cancelled_issues : p.cancelled_estimate_points,
|
|
pending: Math.abs(pending),
|
|
// TODO: This is a temporary logic to show the ideal line in the cycle chart
|
|
ideal: p.date! < today ? ideal(p) : p.date! < cycle.end_date! ? idealToday : null,
|
|
actual: p.date! <= today ? (isBurnDown ? Math.abs(pending) : completed) : undefined,
|
|
};
|
|
});
|
|
return uniqBy(progress, "date");
|
|
};
|