diff --git a/apiserver/plane/app/views/cycle/base.py b/apiserver/plane/app/views/cycle/base.py index f30f498265f..3b2985f18c5 100644 --- a/apiserver/plane/app/views/cycle/base.py +++ b/apiserver/plane/app/views/cycle/base.py @@ -132,6 +132,18 @@ def get_queryset(self): ), ) ) + .annotate( + pending_issues=Count( + "issue_cycle__issue__id", + distinct=True, + filter=Q( + issue_cycle__issue__state__group__in=["backlog", "unstarted", "started"], + issue_cycle__issue__archived_at__isnull=True, + issue_cycle__issue__is_draft=False, + issue_cycle__deleted_at__isnull=True, + ), + ) + ) .annotate( status=Case( When( @@ -214,6 +226,7 @@ def list(self, request, slug, project_id): "is_favorite", "total_issues", "completed_issues", + "pending_issues", "assignee_ids", "status", "version", @@ -245,6 +258,7 @@ def list(self, request, slug, project_id): # meta fields "is_favorite", "total_issues", + "pending_issues", "completed_issues", "assignee_ids", "status", diff --git a/packages/types/src/cycle/cycle.d.ts b/packages/types/src/cycle/cycle.d.ts index 1c2fa273aa9..ee3de40a304 100644 --- a/packages/types/src/cycle/cycle.d.ts +++ b/packages/types/src/cycle/cycle.d.ts @@ -58,6 +58,7 @@ export type TCycleProgress = { export type TProgressSnapshot = { total_issues: number; + pending_issues: number; completed_issues: number; backlog_issues: number; started_issues: number; @@ -120,9 +121,7 @@ export interface CycleIssueResponse { sub_issues_count: number; } -export type SelectCycleType = - | (ICycle & { actionType: "edit" | "delete" | "create-issue" }) - | undefined; +export type SelectCycleType = (ICycle & { actionType: "edit" | "delete" | "create-issue" }) | undefined; export type CycleDateCheckData = { start_date: string; diff --git a/web/ce/components/cycles/end-cycle/modal.tsx b/web/ce/components/cycles/end-cycle/modal.tsx index de66c1a9cbd..62ba16ac733 100644 --- a/web/ce/components/cycles/end-cycle/modal.tsx +++ b/web/ce/components/cycles/end-cycle/modal.tsx @@ -6,7 +6,7 @@ interface Props { cycleId: string; projectId: string; workspaceSlug: string; - transferrableIssuesCount: number; + pendingIssues: number; } export const EndCycleModal: React.FC = () => <>; diff --git a/web/core/components/cycles/list/cycle-list-item-action.tsx b/web/core/components/cycles/list/cycle-list-item-action.tsx index 2fa3d4fd346..ca9fefd4682 100644 --- a/web/core/components/cycles/list/cycle-list-item-action.tsx +++ b/web/core/components/cycles/list/cycle-list-item-action.tsx @@ -81,8 +81,7 @@ export const CycleListItemAction: FC = observer((props) => { // derived values const cycleStatus = cycleDetails.status ? (cycleDetails.status.toLocaleLowerCase() as TCycleGroups) : "draft"; const showIssueCount = useMemo(() => cycleStatus === "draft" || cycleStatus === "upcoming", [cycleStatus]); - const transferableIssuesCount = cycleDetails ? cycleDetails.total_issues - cycleDetails.completed_issues : 0; - const showTransferIssues = transferableIssuesCount > 0 && cycleStatus === "completed"; + const showTransferIssues = cycleDetails.pending_issues > 0 && cycleStatus === "completed"; const isEditingAllowed = allowPermissions( [EUserPermissions.ADMIN, EUserPermissions.MEMBER], EUserPermissionsLevel.PROJECT, @@ -254,7 +253,7 @@ export const CycleListItemAction: FC = observer((props) => { }} > - Transfer {transferableIssuesCount} issues + Transfer {cycleDetails.pending_issues} issues )} diff --git a/web/core/components/cycles/quick-actions.tsx b/web/core/components/cycles/quick-actions.tsx index b72d7b7bd61..405f22676c3 100644 --- a/web/core/components/cycles/quick-actions.tsx +++ b/web/core/components/cycles/quick-actions.tsx @@ -42,7 +42,6 @@ export const CycleQuickActions: React.FC = observer((props) => { const isArchived = !!cycleDetails?.archived_at; const isCompleted = cycleDetails?.status?.toLowerCase() === "completed"; const isCurrentCycle = cycleDetails?.status?.toLowerCase() === "current"; - const transferableIssuesCount = cycleDetails ? cycleDetails.total_issues - cycleDetails.completed_issues : 0; // auth const isEditingAllowed = allowPermissions( [EUserPermissions.ADMIN, EUserPermissions.MEMBER], @@ -176,7 +175,7 @@ export const CycleQuickActions: React.FC = observer((props) => { cycleId={cycleId} projectId={projectId} workspaceSlug={workspaceSlug} - transferrableIssuesCount={transferableIssuesCount} + pendingIssues={cycleDetails.pending_issues} /> )} diff --git a/web/core/store/issue/cycle/issue.store.ts b/web/core/store/issue/cycle/issue.store.ts index 87f5a92127e..6fa9dc70846 100644 --- a/web/core/store/issue/cycle/issue.store.ts +++ b/web/core/store/issue/cycle/issue.store.ts @@ -308,6 +308,14 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues { cycleId as string, payload ); + // update pending count + try { + const cycleMap = this.rootIssueStore.cycleMap; + if (!cycleMap?.[cycleId]) return; + set(cycleMap, [cycleId, "pending_issues"], 0); + } catch (error) { + console.error("Failed to update pending issues count:", error); + } // call fetch issues if (this.paginationOptions) { await persistence.syncIssues(projectId.toString());