[WEB-4953]fix: cycle progress percentage #7826

This commit is contained in:
Vamsi Krishna 2025-09-18 20:21:56 +05:30 committed by GitHub
parent 97059a2786
commit f7d5ca4f83
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 59 additions and 25 deletions

View File

@ -8,7 +8,7 @@ import { Check } from "lucide-react";
import type { TCycleGroups } from "@plane/types";
import { CircularProgressIndicator } from "@plane/ui";
// components
import { generateQueryParams } from "@plane/utils";
import { generateQueryParams, calculateCycleProgress } from "@plane/utils";
import { ListItem } from "@/components/core/list";
// hooks
import { useCycle } from "@/hooks/store/use-cycle";
@ -50,7 +50,6 @@ export const CyclesListItem: FC<TCyclesListItem> = observer((props) => {
// computed
// TODO: change this logic once backend fix the response
const cycleStatus = cycleDetails.status ? (cycleDetails.status.toLocaleLowerCase() as TCycleGroups) : "draft";
const isCompleted = cycleStatus === "completed";
const isActive = cycleStatus === "current";
// handlers
@ -73,20 +72,7 @@ export const CyclesListItem: FC<TCyclesListItem> = observer((props) => {
const handleItemClick = cycleDetails.archived_at ? handleArchivedCycleClick : undefined;
const getCycleProgress = () => {
let completionPercentage =
((cycleDetails.completed_issues + cycleDetails.cancelled_issues) / cycleDetails.total_issues) * 100;
if (isCompleted && !isEmpty(cycleDetails.progress_snapshot)) {
completionPercentage =
((cycleDetails.progress_snapshot.completed_issues + cycleDetails.progress_snapshot.cancelled_issues) /
cycleDetails.progress_snapshot.total_issues) *
100;
}
return isNaN(completionPercentage) ? 0 : Math.floor(completionPercentage);
};
const progress = getCycleProgress();
const progress = calculateCycleProgress(cycleDetails);
return (
<ListItem
@ -96,16 +82,10 @@ export const CyclesListItem: FC<TCyclesListItem> = observer((props) => {
className={className}
prependTitleElement={
<CircularProgressIndicator size={30} percentage={progress} strokeWidth={3}>
{isCompleted ? (
progress === 100 ? (
<Check className="h-3 w-3 stroke-[2] text-custom-primary-100" />
) : (
<span className="text-sm text-custom-primary-100">{`!`}</span>
)
) : progress === 100 ? (
{progress === 100 ? (
<Check className="h-3 w-3 stroke-[2] text-custom-primary-100" />
) : (
<span className="text-[9px] text-custom-text-300">{`${progress}%`}</span>
<span className="text-[9px] text-custom-text-100">{`${progress}%`}</span>
)}
</CircularProgressIndicator>
}

View File

@ -4,7 +4,7 @@ import orderBy from "lodash/orderBy";
import sortBy from "lodash/sortBy";
import uniqBy from "lodash/uniqBy";
// plane imports
import { ICycle, TCycleFilters } from "@plane/types";
import { ICycle, TCycleFilters, TProgressSnapshot } from "@plane/types";
// local imports
import { findTotalDaysInRange, generateDateArray, getDate } from "./datetime";
import { satisfiesDateFilter } from "./filter";
@ -191,3 +191,57 @@ export const formatActiveCycle = (args: {
? formatV1Data(isTypeIssue!, cycle, isBurnDown!, endDate)
: formatV2Data(isTypeIssue!, cycle, isBurnDown!, endDate);
};
/**
* Calculates cycle progress percentage excluding cancelled issues from total count
* Formula: completed / (total - cancelled) * 100
* This gives accurate progress based on: pendingIssues = totalIssues - completedIssues - cancelledIssues
* @param cycle - Cycle data object
* @param estimateType - Whether to calculate based on "issues" or "points"
* @param includeInProgress - Whether to include started/in-progress items in completion calculation
* @returns Progress percentage (0-100)
*/
export const calculateCycleProgress = (
cycle: ICycle | undefined,
estimateType: "issues" | "points" = "issues",
includeInProgress: boolean = false
): number => {
if (!cycle) return 0;
const progressSnapshot: TProgressSnapshot | undefined = cycle.progress_snapshot;
const cycleDetails = progressSnapshot && !isEmpty(progressSnapshot) ? progressSnapshot : cycle;
let completed: number;
let cancelled: number;
let total: number;
if (estimateType === "points") {
completed = cycleDetails.completed_estimate_points || 0;
cancelled = cycleDetails.cancelled_estimate_points || 0;
total = cycleDetails.total_estimate_points || 0;
if (includeInProgress) {
completed += cycleDetails.started_estimate_points || 0;
}
} else {
completed = cycleDetails.completed_issues || 0;
cancelled = cycleDetails.cancelled_issues || 0;
total = cycleDetails.total_issues || 0;
if (includeInProgress) {
completed += cycleDetails.started_issues || 0;
}
}
// Exclude cancelled issues from total (pendingIssues = total - completed - cancelled)
const adjustedTotal = total - cancelled;
// Handle edge cases
if (adjustedTotal === 0) return 0;
if (completed < 0 || adjustedTotal < 0) return 0;
if (completed > adjustedTotal) return 100;
// Calculate percentage and round
const percentage = (completed / adjustedTotal) * 100;
return Math.round(percentage);
};