-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Fix: Cycle graphs refactor #5745
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,8 +1,9 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { startOfToday, format } from "date-fns"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| 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 { findTotalDaysInRange, generateDateArray, getDate } from "@/helpers/date-time.helper"; | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { satisfiesDateFilter } from "@/helpers/filter.helper"; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| export type TProgressChartData = { | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -75,47 +76,97 @@ export const shouldFilterCycle = (cycle: ICycle, filter: TCycleFilters): boolean | |||||||||||||||||||||||||||||||||||||||||||||||
| 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 scope = (p: any, isTypeIssue: boolean) => (isTypeIssue ? p.total_issues : p.total_estimate_points); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid using the Using Apply this diff to specify the parameter type: -const scope = (p: any, isTypeIssue: boolean) => (isTypeIssue ? p.total_issues : p.total_estimate_points);
+interface ProgressData {
+ total_issues?: number;
+ total_estimate_points?: number;
+}
+
+const scope = (p: ProgressData, isTypeIssue: boolean) => (isTypeIssue ? p.total_issues : p.total_estimate_points);📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const ideal = (date: string, scope: number, cycle: ICycle) => | ||||||||||||||||||||||||||||||||||||||||||||||||
| Math.floor( | ||||||||||||||||||||||||||||||||||||||||||||||||
| ((findTotalDaysInRange(date, cycle.end_date) || 0) / | ||||||||||||||||||||||||||||||||||||||||||||||||
| (findTotalDaysInRange(cycle.start_date, cycle.end_date) || 0)) * | ||||||||||||||||||||||||||||||||||||||||||||||||
| scope | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+80
to
+84
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential division by zero in the In the Apply this diff to handle the division by zero: const ideal = (date: string, scope: number, cycle: ICycle) =>
Math.floor(
- ((findTotalDaysInRange(date, cycle.end_date) || 0) /
- (findTotalDaysInRange(cycle.start_date, cycle.end_date) || 0)) *
+ ((findTotalDaysInRange(date, cycle.end_date) || 0) /
+ (findTotalDaysInRange(cycle.start_date, cycle.end_date) || 1)) *
scope
);This change ensures that if the denominator is 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+79
to
+85
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider typing the Explicitly typing the return types and parameters enhances code clarity and TypeScript's type checking capabilities. Specify types for the functions: -const scope = (p: ProgressData, isTypeIssue: boolean) => (isTypeIssue ? p.total_issues : p.total_estimate_points);
+const scope = (p: ProgressData, isTypeIssue: boolean): number => (isTypeIssue ? p.total_issues || 0 : p.total_estimate_points || 0);
-const ideal = (date: string, scope: number, cycle: ICycle) =>
+const ideal = (date: string, scope: number, cycle: ICycle): number =>
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const extendedArray = endDate > today ? generateDateArray(today as Date, endDate) : []; | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (isEmpty(cycle.progress)) return extendedArray; | ||||||||||||||||||||||||||||||||||||||||||||||||
| today = getToday(true); | ||||||||||||||||||||||||||||||||||||||||||||||||
| const formatV1Data = (isTypeIssue: boolean, cycle: ICycle, isBurnDown: boolean, endDate: Date | string) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const today = format(startOfToday(), "yyyy-MM-dd"); | ||||||||||||||||||||||||||||||||||||||||||||||||
| const data = isTypeIssue ? cycle.distribution : cycle.estimate_distribution; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const extendedArray = generateDateArray(endDate, endDate).map((d) => d.date); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (isEmpty(data)) return []; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const progress = [...Object.keys(data.completion_chart), ...extendedArray].map((p) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const pending = data.completion_chart[p] || 0; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const total = isTypeIssue ? cycle.total_issues : cycle.total_estimate_points; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const completed = scope(cycle, isTypeIssue) - pending; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||
| date: p, | ||||||||||||||||||||||||||||||||||||||||||||||||
| scope: p! < today ? scope(cycle, isTypeIssue) : null, | ||||||||||||||||||||||||||||||||||||||||||||||||
| completed, | ||||||||||||||||||||||||||||||||||||||||||||||||
| backlog: isTypeIssue ? cycle.backlog_issues : cycle.backlog_estimate_points, | ||||||||||||||||||||||||||||||||||||||||||||||||
| started: p === today ? cycle[isTypeIssue ? "started_issues" : "started_estimate_points"] : undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||
| unstarted: p === today ? cycle[isTypeIssue ? "unstarted_issues" : "unstarted_estimate_points"] : undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||
| cancelled: p === today ? cycle[isTypeIssue ? "cancelled_issues" : "cancelled_estimate_points"] : undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||
| pending: Math.abs(pending || 0), | ||||||||||||||||||||||||||||||||||||||||||||||||
| ideal: | ||||||||||||||||||||||||||||||||||||||||||||||||
| p < today | ||||||||||||||||||||||||||||||||||||||||||||||||
| ? ideal(p, total || 0, cycle) | ||||||||||||||||||||||||||||||||||||||||||||||||
| : p <= cycle.end_date! | ||||||||||||||||||||||||||||||||||||||||||||||||
| ? ideal(today as string, total || 0, cycle) | ||||||||||||||||||||||||||||||||||||||||||||||||
| : null, | ||||||||||||||||||||||||||||||||||||||||||||||||
| actual: p <= today ? (isBurnDown ? Math.abs(pending) : completed) : undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| 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)); | ||||||||||||||||||||||||||||||||||||||||||||||||
| return progress; | ||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const formatV2Data = (isTypeIssue: boolean, cycle: ICycle, isBurnDown: boolean, endDate: Date | string) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (!cycle.progress) return []; | ||||||||||||||||||||||||||||||||||||||||||||||||
| let today: Date | string = startOfToday(); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const scopeToday = scope(cycle?.progress[cycle?.progress.length - 1]); | ||||||||||||||||||||||||||||||||||||||||||||||||
| const idealToday = ideal(cycle?.progress[cycle?.progress.length - 1]); | ||||||||||||||||||||||||||||||||||||||||||||||||
| const extendedArray = endDate > today ? generateDateArray(today as Date, endDate) : []; | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (isEmpty(cycle.progress)) return extendedArray; | ||||||||||||||||||||||||||||||||||||||||||||||||
| today = format(startOfToday(), "yyyy-MM-dd"); | ||||||||||||||||||||||||||||||||||||||||||||||||
| const todaysData = cycle?.progress[cycle?.progress.length - 1]; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const scopeToday = scope(todaysData, isTypeIssue); | ||||||||||||||||||||||||||||||||||||||||||||||||
| const idealToday = ideal(todaysData.date, scopeToday, cycle); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const progress = [...orderBy(cycle?.progress, "date"), ...extendedArray].map((p) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| let 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; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const dataDate = p.progress_date ? format(new Date(p.progress_date), "yyyy-MM-dd") : p.date; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||||||||||||||||||||
| date: p.date, | ||||||||||||||||||||||||||||||||||||||||||||||||
| scope: p.date! < today ? scope(p) : p.date! < cycle.end_date! ? scopeToday : null, | ||||||||||||||||||||||||||||||||||||||||||||||||
| date: dataDate, | ||||||||||||||||||||||||||||||||||||||||||||||||
| scope: dataDate! < today ? scope(p, isTypeIssue) : dataDate! <= 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, | ||||||||||||||||||||||||||||||||||||||||||||||||
| ideal: | ||||||||||||||||||||||||||||||||||||||||||||||||
| dataDate! < today | ||||||||||||||||||||||||||||||||||||||||||||||||
| ? ideal(dataDate, scope(p, isTypeIssue), cycle) | ||||||||||||||||||||||||||||||||||||||||||||||||
| : dataDate! < cycle.end_date! | ||||||||||||||||||||||||||||||||||||||||||||||||
| ? idealToday | ||||||||||||||||||||||||||||||||||||||||||||||||
| : null, | ||||||||||||||||||||||||||||||||||||||||||||||||
| actual: dataDate! <= today ? (isBurnDown ? Math.abs(pending) : completed) : undefined, | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+131
to
+153
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle different types of In Separate the handling of progress data and extended dates: let progressData = orderBy(cycle.progress, "date").map((p) => {
// existing logic for progress entries
});
+let extendedProgress = extendedArray.map((date) => ({
+ date: format(new Date(date), "yyyy-MM-dd"),
+ scope: date <= cycle.end_date! ? scopeToday : null,
+ // other properties set to default or null
+}));
-progress = [...progressData, ...extendedArray].map((p) => { /* ... */ });
+progress = [...progressData, ...extendedProgress];
progress = uniqBy(progress, "date");This ensures that the properties are correctly assigned based on whether
|
||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||
| return uniqBy(progress, "date"); | ||||||||||||||||||||||||||||||||||||||||||||||||
| progress = uniqBy(progress, "date"); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return progress; | ||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+120
to
+159
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Possible property access on When accessing Apply this diff to add a safety check: let today: Date | string = startOfToday();
const extendedArray = endDate > today ? generateDateArray(today as Date, endDate) : [];
if (isEmpty(cycle.progress)) return extendedArray;
today = format(startOfToday(), "yyyy-MM-dd");
-const todaysData = cycle?.progress[cycle?.progress.length - 1];
+const todaysData = cycle.progress[cycle.progress.length - 1];
+if (!todaysData) return [];
const scopeToday = scope(todaysData, isTypeIssue);
const idealToday = ideal(todaysData.date, scopeToday, cycle);This ensures that the function returns early if
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| export const formatActiveCycle = (args: { | ||||||||||||||||||||||||||||||||||||||||||||||||
| cycle: ICycle; | ||||||||||||||||||||||||||||||||||||||||||||||||
| isBurnDown?: boolean | undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||
| isTypeIssue?: boolean | undefined; | ||||||||||||||||||||||||||||||||||||||||||||||||
| }) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const { cycle, isBurnDown, isTypeIssue } = args; | ||||||||||||||||||||||||||||||||||||||||||||||||
| const endDate: Date | string = new Date(cycle.end_date!); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return cycle.version === 1 | ||||||||||||||||||||||||||||||||||||||||||||||||
| ? formatV1Data(isTypeIssue!, cycle, isBurnDown!, endDate) | ||||||||||||||||||||||||||||||||||||||||||||||||
| : formatV2Data(isTypeIssue!, cycle, isBurnDown!, endDate); | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+161
to
+171
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Avoid using non-null assertions with potentially undefined values. In Provide default values to ensure safety: export const formatActiveCycle = (args: {
cycle: ICycle;
isBurnDown?: boolean;
isTypeIssue?: boolean;
}) => {
const { cycle, isBurnDown = false, isTypeIssue = false } = args;
const endDate: Date | string = new Date(cycle.end_date!);
return cycle.version === 1
- ? formatV1Data(isTypeIssue!, cycle, isBurnDown!, endDate)
- : formatV2Data(isTypeIssue!, cycle, isBurnDown!, endDate);
+ ? formatV1Data(isTypeIssue, cycle, isBurnDown, endDate)
+ : formatV2Data(isTypeIssue, cycle, isBurnDown, endDate);
};This approach removes the need for non-null assertions and sets default values if they are not provided. 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Implement the
fetchActiveCycleProgressPromethod.The
fetchActiveCycleProgressPromethod has been replaced with an empty async function. This implementation doesn't match the method's name or its expected behavior. Please implement the method to fetch the active cycle progress for pro users.Consider implementing the method similar to the non-pro version, adjusting for any pro-specific features: