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 0aa3fc2804e..e325b81ad6b 100644 --- a/packages/types/src/cycle/cycle.d.ts +++ b/packages/types/src/cycle/cycle.d.ts @@ -104,6 +104,7 @@ export interface ICycle extends TProgressSnapshot { project_detail: IProjectDetails; progress: any[]; version: number; + pending_issues: number; } export interface CycleIssueResponse { @@ -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/core/components/cycles/list/cycle-list-item-action.tsx b/web/core/components/cycles/list/cycle-list-item-action.tsx index 1934acf4988..a627353fb77 100644 --- a/web/core/components/cycles/list/cycle-list-item-action.tsx +++ b/web/core/components/cycles/list/cycle-list-item-action.tsx @@ -82,9 +82,11 @@ 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 = routerProjectId && transferableIssuesCount > 0 && cycleStatus === "completed"; // Only available inside project view. + + const showTransferIssues = cycleDetails.pending_issues > 0 && cycleStatus === "completed"; + const isEditingAllowed = allowPermissions( [EUserPermissions.ADMIN, EUserPermissions.MEMBER], EUserPermissionsLevel.PROJECT, @@ -256,7 +258,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..0f6a27979f0 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], @@ -170,14 +169,16 @@ export const CycleQuickActions: React.FC = observer((props) => { workspaceSlug={workspaceSlug} projectId={projectId} /> - setEndCycleModalOpen(false)} - cycleId={cycleId} - projectId={projectId} - workspaceSlug={workspaceSlug} - transferrableIssuesCount={transferableIssuesCount} - /> + {isCurrentCycle && ( + setEndCycleModalOpen(false)} + cycleId={cycleId} + projectId={projectId} + workspaceSlug={workspaceSlug} + transferrableIssuesCount={cycleDetails.pending_issues} + /> + )} )} diff --git a/web/core/components/cycles/transfer-issues-modal.tsx b/web/core/components/cycles/transfer-issues-modal.tsx index fc069687321..0e9b56c0f75 100644 --- a/web/core/components/cycles/transfer-issues-modal.tsx +++ b/web/core/components/cycles/transfer-issues-modal.tsx @@ -26,7 +26,7 @@ export const TransferIssuesModal: React.FC = observer((props) => { const [query, setQuery] = useState(""); // store hooks - const { currentProjectIncompleteCycleIds, getCycleById, fetchCycleDetails } = useCycle(); + const { currentProjectIncompleteCycleIds, getCycleById, fetchActiveCycleProgress } = useCycle(); const { issues: { transferIssuesFromCycle }, } = useIssues(EIssuesStoreType.CYCLE); @@ -57,8 +57,8 @@ export const TransferIssuesModal: React.FC = observer((props) => { /**To update issue counts in target cycle and current cycle */ const getCycleDetails = async (newCycleId: string) => { const cyclesFetch = [ - fetchCycleDetails(workspaceSlug.toString(), projectId.toString(), cycleId), - fetchCycleDetails(workspaceSlug.toString(), projectId.toString(), newCycleId), + fetchActiveCycleProgress(workspaceSlug.toString(), projectId.toString(), cycleId), + fetchActiveCycleProgress(workspaceSlug.toString(), projectId.toString(), newCycleId), ]; await Promise.all(cyclesFetch).catch((error) => { setToast({