From 6dbab0dae452ba659978f97c5d5f3f1352591ed2 Mon Sep 17 00:00:00 2001 From: sangeethailango Date: Wed, 7 Jan 2026 20:18:52 +0530 Subject: [PATCH 1/6] chore: add intake_count in project list endpoint --- apps/api/plane/app/views/project/base.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/api/plane/app/views/project/base.py b/apps/api/plane/app/views/project/base.py index e1d0c0c2a6e..945bf0f5f48 100644 --- a/apps/api/plane/app/views/project/base.py +++ b/apps/api/plane/app/views/project/base.py @@ -4,7 +4,7 @@ # Django imports from django.core.serializers.json import DjangoJSONEncoder -from django.db.models import Exists, F, OuterRef, Prefetch, Q, Subquery +from django.db.models import Exists, F, OuterRef, Prefetch, Q, Subquery, Count from django.utils import timezone # Third Party imports @@ -24,7 +24,6 @@ from plane.db.models import ( UserFavorite, DeployBoard, - ProjectUserProperty, Intake, Project, ProjectIdentifier, @@ -32,9 +31,9 @@ ProjectNetwork, State, DEFAULT_STATES, - UserFavorite, Workspace, WorkspaceMember, + IntakeIssue, ) from plane.utils.host import base_host @@ -153,6 +152,7 @@ def list(self, request, slug): is_active=True, ).values("role") ) + .annotate(intake_count=Count("project_intakeissue")) .annotate(inbox_view=F("intake_view")) .annotate(sort_order=Subquery(sort_order)) .distinct() @@ -163,6 +163,7 @@ def list(self, request, slug): "sort_order", "logo_props", "member_role", + "intake_count", "archived_at", "workspace", "cycle_view", From 98c86b5714773643fb72beb789806f0fbdc31345 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Thu, 8 Jan 2026 12:39:45 +0530 Subject: [PATCH 2/6] chore: sidebar project navigation intake count added --- .../workspace/sidebar/project-navigation.tsx | 13 ++++++++++--- packages/types/src/project/projects.ts | 1 + 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/apps/web/core/components/workspace/sidebar/project-navigation.tsx b/apps/web/core/components/workspace/sidebar/project-navigation.tsx index 481f5929e6b..93d1020754e 100644 --- a/apps/web/core/components/workspace/sidebar/project-navigation.tsx +++ b/apps/web/core/components/workspace/sidebar/project-navigation.tsx @@ -171,12 +171,19 @@ export const ProjectNavigation = observer(function ProjectNavigation(props: TPro const hasAccess = allowPermissions(item.access, EUserPermissionsLevel.PROJECT, workspaceSlug, project.id); if (!hasAccess) return null; + const shouldShowCount = item.key === "intake" && (project.intake_count ?? 0) > 0; + return ( -
- - {t(item.i18n_key)} +
+
+ + {t(item.i18n_key)} +
+ {shouldShowCount && {project.intake_count}}
diff --git a/packages/types/src/project/projects.ts b/packages/types/src/project/projects.ts index 4258cf725ac..6352a26c84c 100644 --- a/packages/types/src/project/projects.ts +++ b/packages/types/src/project/projects.ts @@ -33,6 +33,7 @@ export interface IPartialProject { // actor created_by?: string; updated_by?: string; + intake_count?: number; } export interface IProject extends IPartialProject { From 309cf6fb0ae19eb9d13f9abd7d60c0f7584abfe0 Mon Sep 17 00:00:00 2001 From: sangeethailango Date: Thu, 8 Jan 2026 13:03:01 +0530 Subject: [PATCH 3/6] fix: filter out closed intake issues in the count --- apps/api/plane/app/views/project/base.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/api/plane/app/views/project/base.py b/apps/api/plane/app/views/project/base.py index 945bf0f5f48..871315bf56c 100644 --- a/apps/api/plane/app/views/project/base.py +++ b/apps/api/plane/app/views/project/base.py @@ -35,6 +35,7 @@ WorkspaceMember, IntakeIssue, ) +from plane.db.models.intake import IntakeIssueStatus from plane.utils.host import base_host @@ -152,7 +153,11 @@ def list(self, request, slug): is_active=True, ).values("role") ) - .annotate(intake_count=Count("project_intakeissue")) + .annotate( + intake_count=Count( + "project_intakeissue", filter=Q(project_intakeissue__status=IntakeIssueStatus.PENDING.value) + ) + ) .annotate(inbox_view=F("intake_view")) .annotate(sort_order=Subquery(sort_order)) .distinct() From 88976d712676966c0cb28f304f5811242703c21c Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Thu, 8 Jan 2026 14:47:23 +0530 Subject: [PATCH 4/6] chore: code refactor --- .../web/core/store/inbox/inbox-issue.store.ts | 31 ++++++++++++++++++- .../core/store/inbox/project-inbox.store.ts | 11 +++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/apps/web/core/store/inbox/inbox-issue.store.ts b/apps/web/core/store/inbox/inbox-issue.store.ts index 5c099a66882..a76deddaf0b 100644 --- a/apps/web/core/store/inbox/inbox-issue.store.ts +++ b/apps/web/core/store/inbox/inbox-issue.store.ts @@ -94,6 +94,7 @@ export class InboxIssueStore implements IInboxIssueStore { const previousData: Partial = { status: this.status, }; + const previousStatus = this.status; try { if (!this.issue.id) return; @@ -101,7 +102,20 @@ export class InboxIssueStore implements IInboxIssueStore { const inboxIssue = await this.inboxIssueService.update(this.workspaceSlug, this.projectId, this.issue.id, { status: status, }); - runInAction(() => set(this, "status", inboxIssue?.status)); + runInAction(() => { + set(this, "status", inboxIssue?.status); + + // Handle intake_count transitions + if (previousStatus === EInboxIssueStatus.PENDING && inboxIssue.status !== EInboxIssueStatus.PENDING) { + // Changed from PENDING to something else: decrement + const currentCount = this.store.projectRoot.project.projectMap[this.projectId]?.intake_count ?? 0; + set(this.store.projectRoot.project.projectMap, [this.projectId, "intake_count"], Math.max(0, currentCount - 1)); + } else if (previousStatus !== EInboxIssueStatus.PENDING && inboxIssue.status === EInboxIssueStatus.PENDING) { + // Changed from something else to PENDING: increment + const currentCount = this.store.projectRoot.project.projectMap[this.projectId]?.intake_count ?? 0; + set(this.store.projectRoot.project.projectMap, [this.projectId, "intake_count"], currentCount + 1); + } + }); // If issue accepted sync issue to local db if (status === EInboxIssueStatus.ACCEPTED) { @@ -120,6 +134,7 @@ export class InboxIssueStore implements IInboxIssueStore { duplicate_to: this.duplicate_to, duplicate_issue_detail: this.duplicate_issue_detail, }; + const wasPending = this.status === EInboxIssueStatus.PENDING; try { if (!this.issue.id) return; const inboxIssue = await this.inboxIssueService.update(this.workspaceSlug, this.projectId, this.issue.id, { @@ -130,6 +145,11 @@ export class InboxIssueStore implements IInboxIssueStore { set(this, "status", inboxIssue?.status); set(this, "duplicate_to", inboxIssue?.duplicate_to); set(this, "duplicate_issue_detail", inboxIssue?.duplicate_issue_detail); + // Decrement intake_count if the issue was PENDING + if (wasPending) { + const currentCount = this.store.projectRoot.project.projectMap[this.projectId]?.intake_count ?? 0; + set(this.store.projectRoot.project.projectMap, [this.projectId, "intake_count"], Math.max(0, currentCount - 1)); + } }); } catch { runInAction(() => { @@ -146,6 +166,7 @@ export class InboxIssueStore implements IInboxIssueStore { status: this.status, snoozed_till: this.snoozed_till, }; + const previousStatus = this.status; try { if (!this.issue.id) return; const inboxIssue = await this.inboxIssueService.update(this.workspaceSlug, this.projectId, this.issue.id, { @@ -155,6 +176,14 @@ export class InboxIssueStore implements IInboxIssueStore { runInAction(() => { set(this, "status", inboxIssue?.status); set(this, "snoozed_till", inboxIssue?.snoozed_till); + // Handle intake_count transitions + if (previousStatus === EInboxIssueStatus.PENDING && inboxIssue.status === EInboxIssueStatus.SNOOZED) { + const currentCount = this.store.projectRoot.project.projectMap[this.projectId]?.intake_count ?? 0; + set(this.store.projectRoot.project.projectMap, [this.projectId, "intake_count"], Math.max(0, currentCount - 1)); + } else if (previousStatus !== EInboxIssueStatus.PENDING && inboxIssue.status === EInboxIssueStatus.PENDING) { + const currentCount = this.store.projectRoot.project.projectMap[this.projectId]?.intake_count ?? 0; + set(this.store.projectRoot.project.projectMap, [this.projectId, "intake_count"], currentCount + 1); + } }); } catch { runInAction(() => { diff --git a/apps/web/core/store/inbox/project-inbox.store.ts b/apps/web/core/store/inbox/project-inbox.store.ts index 788db2dc7fc..9f21418284e 100644 --- a/apps/web/core/store/inbox/project-inbox.store.ts +++ b/apps/web/core/store/inbox/project-inbox.store.ts @@ -467,6 +467,11 @@ export class ProjectInboxStore implements IProjectInboxStore { ["inboxIssuePaginationInfo", "total_results"], (this.inboxIssuePaginationInfo?.total_results || 0) + 1 ); + // Increment intake_count if the new issue is PENDING + if (inboxIssueResponse.status === EInboxIssueStatus.PENDING) { + const currentCount = this.store.projectRoot.project.projectMap[projectId]?.intake_count ?? 0; + set(this.store.projectRoot.project.projectMap, [projectId, "intake_count"], currentCount + 1); + } }); return inboxIssueResponse; } catch (error) { @@ -483,6 +488,7 @@ export class ProjectInboxStore implements IProjectInboxStore { */ deleteInboxIssue = async (workspaceSlug: string, projectId: string, inboxIssueId: string) => { const currentIssue = this.inboxIssues?.[inboxIssueId]; + const wasPending = currentIssue?.status === EInboxIssueStatus.PENDING; try { if (!currentIssue) return; await this.inboxIssueService.destroy(workspaceSlug, projectId, inboxIssueId).then(() => { @@ -498,6 +504,11 @@ export class ProjectInboxStore implements IProjectInboxStore { ["inboxIssueIds"], this.inboxIssueIds.filter((id) => id !== inboxIssueId) ); + // Decrement intake_count if the deleted issue was PENDING + if (wasPending) { + const currentCount = this.store.projectRoot.project.projectMap[projectId]?.intake_count ?? 0; + set(this.store.projectRoot.project.projectMap, [projectId, "intake_count"], Math.max(0, currentCount - 1)); + } }); }); } catch (error) { From 413fc447f60f8c8abf418f8d5f319c4aad791da2 Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia Date: Thu, 8 Jan 2026 14:50:22 +0530 Subject: [PATCH 5/6] chore: code refactor --- apps/web/core/store/inbox/inbox-issue.store.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/apps/web/core/store/inbox/inbox-issue.store.ts b/apps/web/core/store/inbox/inbox-issue.store.ts index a76deddaf0b..a1758936d61 100644 --- a/apps/web/core/store/inbox/inbox-issue.store.ts +++ b/apps/web/core/store/inbox/inbox-issue.store.ts @@ -109,7 +109,11 @@ export class InboxIssueStore implements IInboxIssueStore { if (previousStatus === EInboxIssueStatus.PENDING && inboxIssue.status !== EInboxIssueStatus.PENDING) { // Changed from PENDING to something else: decrement const currentCount = this.store.projectRoot.project.projectMap[this.projectId]?.intake_count ?? 0; - set(this.store.projectRoot.project.projectMap, [this.projectId, "intake_count"], Math.max(0, currentCount - 1)); + set( + this.store.projectRoot.project.projectMap, + [this.projectId, "intake_count"], + Math.max(0, currentCount - 1) + ); } else if (previousStatus !== EInboxIssueStatus.PENDING && inboxIssue.status === EInboxIssueStatus.PENDING) { // Changed from something else to PENDING: increment const currentCount = this.store.projectRoot.project.projectMap[this.projectId]?.intake_count ?? 0; @@ -148,7 +152,11 @@ export class InboxIssueStore implements IInboxIssueStore { // Decrement intake_count if the issue was PENDING if (wasPending) { const currentCount = this.store.projectRoot.project.projectMap[this.projectId]?.intake_count ?? 0; - set(this.store.projectRoot.project.projectMap, [this.projectId, "intake_count"], Math.max(0, currentCount - 1)); + set( + this.store.projectRoot.project.projectMap, + [this.projectId, "intake_count"], + Math.max(0, currentCount - 1) + ); } }); } catch { @@ -179,7 +187,11 @@ export class InboxIssueStore implements IInboxIssueStore { // Handle intake_count transitions if (previousStatus === EInboxIssueStatus.PENDING && inboxIssue.status === EInboxIssueStatus.SNOOZED) { const currentCount = this.store.projectRoot.project.projectMap[this.projectId]?.intake_count ?? 0; - set(this.store.projectRoot.project.projectMap, [this.projectId, "intake_count"], Math.max(0, currentCount - 1)); + set( + this.store.projectRoot.project.projectMap, + [this.projectId, "intake_count"], + Math.max(0, currentCount - 1) + ); } else if (previousStatus !== EInboxIssueStatus.PENDING && inboxIssue.status === EInboxIssueStatus.PENDING) { const currentCount = this.store.projectRoot.project.projectMap[this.projectId]?.intake_count ?? 0; set(this.store.projectRoot.project.projectMap, [this.projectId, "intake_count"], currentCount + 1); From ddb87256825c68a1abf5f1fa2866b6204f3d168d Mon Sep 17 00:00:00 2001 From: sangeethailango Date: Thu, 8 Jan 2026 15:12:47 +0530 Subject: [PATCH 6/6] fix: filter out deleted intake issues --- apps/api/plane/app/views/project/base.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/apps/api/plane/app/views/project/base.py b/apps/api/plane/app/views/project/base.py index 871315bf56c..ac63bdc95af 100644 --- a/apps/api/plane/app/views/project/base.py +++ b/apps/api/plane/app/views/project/base.py @@ -33,7 +33,6 @@ DEFAULT_STATES, Workspace, WorkspaceMember, - IntakeIssue, ) from plane.db.models.intake import IntakeIssueStatus from plane.utils.host import base_host @@ -155,7 +154,11 @@ def list(self, request, slug): ) .annotate( intake_count=Count( - "project_intakeissue", filter=Q(project_intakeissue__status=IntakeIssueStatus.PENDING.value) + "project_intakeissue", + filter=Q( + project_intakeissue__status=IntakeIssueStatus.PENDING.value, + project_intakeissue__deleted_at__isnull=True, + ), ) ) .annotate(inbox_view=F("intake_view"))