Skip to content

Conversation

@sangeethailango
Copy link
Member

@sangeethailango sangeethailango commented Jan 7, 2026

Description

This PR will show the intake count for projects

Below is the number of queries the project list endpoint hits with the added logic to fetch the intake count. Tested with more than hundreds of intake issues and no N+1 queries.

Screenshot 2026-01-08 at 2 43 56 PM

Type of Change

  • Improvement (change that would cause existing functionality to not work as expected)

Summary by CodeRabbit

  • New Features

    • Projects now include an intake issue count in API responses and project data.
    • Sidebar navigation shows a right-aligned intake badge when a project has pending intake issues.
    • Intake counts update automatically as inbox issues are created, deleted, or their status changes, keeping badges and counts in sync.
  • Chores

    • Project types updated to include an optional intake_count field.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 7, 2026

📝 Walkthrough

Walkthrough

Adds a pending-intake aggregation to Project API responses, exposes intake_count in the TypeScript project type, displays a conditional intake badge in the web sidebar when >0, and updates inbox stores to increment/decrement intake_count when issues enter/leave the PENDING state.

Changes

Cohort / File(s) Summary
Project API: annotate & expose pending intake_count
apps/api/plane/app/views/project/base.py
Import Count and IntakeIssueStatus; annotate Project queryset with intake_count = Count("project_intakeissue", filter=Q(project_intakeissue__status=IntakeIssueStatus.PENDING.value, project_intakeissue__deleted_at__isnull=True)); include intake_count in values(...) projection.
Frontend: sidebar intake badge
apps/web/core/components/workspace/sidebar/project-navigation.tsx
Render a right-aligned intake count badge when item.key === "intake" and project.intake_count > 0; adjust item layout to align label and badge while preserving access/active-state logic.
Types: project shape updated
packages/types/src/project/projects.ts
Add optional intake_count?: number to IPartialProject.
Inbox stores: adjust project intake_count on status changes
apps/web/core/store/inbox/inbox-issue.store.ts, apps/web/core/store/inbox/project-inbox.store.ts
On create/delete and status updates (status change, duplicate, snooze), capture previous status and conditionally increment/decrement the owning project's intake_count when issues enter or leave the PENDING state; guard against negative counts.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Client (UI)
  participant Sidebar as Frontend Sidebar
  participant Store as Frontend Store
  participant API as Project/Inbox API
  participant DB as Database

  Browser ->> Sidebar: render workspace
  Sidebar ->> API: GET /projects (request intake_count)
  API ->> DB: query Projects with Count(project_intakeissue where status=PENDING) as intake_count
  DB -->> API: project rows with intake_count
  API -->> Sidebar: projects JSON (includes intake_count)
  Sidebar ->> Browser: render navigation, show badge when intake_count > 0

  Browser ->> Store: create/update/delete inbox issue
  Store ->> API: POST/PUT/DELETE /inbox-issues
  API ->> DB: persist change, return issue with status
  DB -->> API: result
  API -->> Store: issue response
  Store ->> Store: if status transitioned to/from PENDING, adjust project.intake_count locally
  Store ->> Sidebar: propagate projectMap change
  Sidebar ->> Browser: update badge count
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐇 I hopped through rows and counted pending things,
A tiny badge now on the sidebar springs.
From DB to UI my little paws prance,
Toggling counts with each status dance.
Hooray — the intake tally gets its chance.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The description covers the key objective and includes a screenshot demonstrating the feature. However, the Type of Change selection appears inconsistent with the PR content. Clarify the Type of Change: this appears to be a Feature (new functionality) rather than an Improvement. Additionally, add a Test Scenarios section describing how the intake count was validated.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding intake count display for projects across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
apps/api/plane/app/views/project/base.py (2)

36-36: Consider removing unused import.

The IntakeIssue model is imported but not directly referenced in this file. The Count("project_intakeissue") annotation uses the related name string directly, so this import isn't required.


137-207: Consider adding intake_count to other list methods for consistency.

The intake_count annotation is added only in the list() method but not in list_detail() (line 97) or the base get_queryset() method (line 47). This creates an inconsistency where different endpoints return different fields.

If intake_count is a useful metric, consider whether it should be available in list_detail() as well, either by:

  • Adding the annotation to get_queryset() (though this would affect all queryset usages)
  • Adding the annotation in list_detail() similar to how it's done in list()
  • Ensuring ProjectListSerializer can handle the field if it's added to the queryset
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b83d460 and 6dbab0d.

📒 Files selected for processing (1)
  • apps/api/plane/app/views/project/base.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-29T09:17:54.815Z
Learnt from: NarayanBavisetti
Repo: makeplane/plane PR: 7905
File: apps/api/plane/app/views/search/base.py:241-276
Timestamp: 2025-10-29T09:17:54.815Z
Learning: In apps/api/plane/app/views/search/base.py, the `filter_intakes` method uses `Issue.objects` (base manager) instead of `Issue.issue_objects` (custom manager) because the custom manager filters out all intake statuses, which would prevent querying pending and snoozed intake issues.

Applied to files:

  • apps/api/plane/app/views/project/base.py
📚 Learning: 2025-12-23T14:18:32.899Z
Learnt from: dheeru0198
Repo: makeplane/plane PR: 8339
File: apps/api/plane/db/models/api.py:35-35
Timestamp: 2025-12-23T14:18:32.899Z
Learning: Django REST Framework rate limit strings are flexible: only the first character of the time unit matters. Acceptable formats include: "60/s", "60/sec", "60/second" (all equivalent), "60/m", "60/min", "60/minute" (all equivalent), "60/h", "60/hr", "60/hour" (all equivalent), and "60/d", "60/day" (all equivalent). Abbreviations like "min" are valid and do not need to be changed to "minute". Apply this guidance to any Python files in the project that configure DRF throttling rules.

Applied to files:

  • apps/api/plane/app/views/project/base.py
🧬 Code graph analysis (1)
apps/api/plane/app/views/project/base.py (1)
apps/api/plane/db/models/intake.py (1)
  • IntakeIssue (46-80)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
apps/api/plane/app/views/project/base.py (2)

166-166: LGTM!

Correctly includes the annotated intake_count field in the values projection.


155-155: The annotation Count("project_intakeissue") is correct. The related_name is automatically generated by ProjectBaseModel, which defines related_name="project_%(class)s" on the project foreign key. For IntakeIssue, this expands to "project_intakeissue".

Likely an incorrect or invalid review comment.

@sangeethailango sangeethailango self-assigned this Jan 8, 2026
@anmolsinghbhatia anmolsinghbhatia changed the title chore: added intake count for projects [WEB-5871] chore: added intake count for projects Jan 8, 2026
@makeplane
Copy link

makeplane bot commented Jan 8, 2026

Linked to Plane Work Item(s)

This comment was auto-generated by Plane

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
apps/web/core/components/workspace/sidebar/project-navigation.tsx (1)

179-187: Consider adding accessibility attributes for the count badge.

The layout restructure works well and the conditional count display is implemented correctly. However, consider adding an aria-label to the count badge for better screen reader support.

♿ Optional accessibility enhancement
-                {shouldShowCount && <span className="text-11 font-medium text-tertiary">{project.intake_count}</span>}
+                {shouldShowCount && (
+                  <span className="text-11 font-medium text-tertiary" aria-label={`${project.intake_count} intake items`}>
+                    {project.intake_count}
+                  </span>
+                )}
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6dbab0d and 98c86b5.

📒 Files selected for processing (2)
  • apps/web/core/components/workspace/sidebar/project-navigation.tsx
  • packages/types/src/project/projects.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,mts,cts}

📄 CodeRabbit inference engine (.github/instructions/typescript.instructions.md)

**/*.{ts,tsx,mts,cts}: Use const type parameters for more precise literal inference in TypeScript 5.0+
Use the satisfies operator to validate types without widening them
Leverage inferred type predicates to reduce the need for explicit is return types in filter/check functions
Use NoInfer<T> utility to block inference for specific type arguments when they should be determined by other arguments
Utilize narrowing in switch(true) blocks for control flow analysis (TypeScript 5.3+)
Rely on narrowing from direct boolean comparisons for type guards
Trust preserved narrowing in closures when variables aren't modified after the check (TypeScript 5.4+)
Use constant indices to narrow object/array properties (TypeScript 5.5+)
Use standard ECMAScript decorators (Stage 3) instead of legacy experimentalDecorators
Use using declarations for explicit resource management with Disposable pattern instead of manual cleanup (TypeScript 5.2+)
Use with { type: "json" } for import attributes; avoid deprecated assert syntax (TypeScript 5.3/5.8+)
Use import type explicitly when importing types to ensure they are erased during compilation, respecting verbatimModuleSyntax flag
Use .ts, .mts, .cts extensions in import type statements (TypeScript 5.2+)
Use import type { Type } from "mod" with { "resolution-mode": "import" } for specific module resolution contexts (TypeScript 5.3+)
Use new iterator methods (map, filter, etc.) if targeting modern environments (TypeScript 5.6+)
Utilize new Set methods like union, intersection, etc., when available (TypeScript 5.5+)
Use Object.groupBy / Map.groupBy standard methods for grouping instead of external libraries (TypeScript 5.4+)
Use Promise.withResolvers() for creating promises with exposed resolve/reject functions (TypeScript 5.7+)
Use copying array methods (toSorted, toSpliced, with) for immutable array operations (TypeScript 5.2+)
Avoid accessing instance fields via super in classes (TypeScript 5....

Files:

  • apps/web/core/components/workspace/sidebar/project-navigation.tsx
  • packages/types/src/project/projects.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Enable TypeScript strict mode and ensure all files are fully typed

Files:

  • apps/web/core/components/workspace/sidebar/project-navigation.tsx
  • packages/types/src/project/projects.ts
**/*.{js,jsx,ts,tsx,json,css}

📄 CodeRabbit inference engine (AGENTS.md)

Use Prettier with Tailwind plugin for code formatting, run pnpm fix:format

Files:

  • apps/web/core/components/workspace/sidebar/project-navigation.tsx
  • packages/types/src/project/projects.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,jsx,ts,tsx}: Use ESLint with shared config across packages, adhering to max warnings limits per package
Use camelCase for variable and function names, PascalCase for components and types
Use try-catch with proper error types and log errors appropriately

Files:

  • apps/web/core/components/workspace/sidebar/project-navigation.tsx
  • packages/types/src/project/projects.ts
🧠 Learnings (2)
📚 Learning: 2025-12-17T10:58:59.591Z
Learnt from: CR
Repo: makeplane/plane PR: 0
File: packages/tailwind-config/AGENTS.md:0-0
Timestamp: 2025-12-17T10:58:59.591Z
Learning: Applies to packages/tailwind-config/**/*.{ts,tsx} : Sidebar menu items should use transparent backgrounds with hover states like `hover:bg-layer-1-hover` rather than base layer backgrounds, maintaining visual lightness while providing hover feedback

Applied to files:

  • apps/web/core/components/workspace/sidebar/project-navigation.tsx
📚 Learning: 2025-06-16T07:23:39.497Z
Learnt from: vamsikrishnamathala
Repo: makeplane/plane PR: 7214
File: web/core/store/issue/helpers/base-issues.store.ts:117-117
Timestamp: 2025-06-16T07:23:39.497Z
Learning: In the updateIssueDates method of BaseIssuesStore (web/core/store/issue/helpers/base-issues.store.ts), the projectId parameter is intentionally made optional to support override implementations in subclasses. The base implementation requires projectId and includes an early return check, but making it optional allows derived classes to override the method with different parameter requirements.

Applied to files:

  • packages/types/src/project/projects.ts
🧬 Code graph analysis (1)
apps/web/core/components/workspace/sidebar/project-navigation.tsx (2)
apps/web/core/components/sidebar/sidebar-navigation.tsx (1)
  • SidebarNavItem (9-25)
packages/i18n/src/store/index.ts (1)
  • t (222-243)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Build packages
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
packages/types/src/project/projects.ts (1)

36-36: LGTM! Well-typed optional field addition.

The optional intake_count field is appropriately typed and maintains backward compatibility. The snake_case convention matches the existing interface style.

apps/web/core/components/workspace/sidebar/project-navigation.tsx (1)

174-174: LGTM! Correct nullish coalescing usage.

The logic correctly handles the optional intake_count field using the nullish coalescing operator and only displays the count for the intake item when it's greater than zero.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @apps/api/plane/app/views/project/base.py:
- Around line 156-160: The intake_count annotation is only added in list()
causing missing field in list_detail() and retrieve(); move the Count(...)
annotation into get_queryset() so every endpoint using self.get_queryset()
(including list_detail and retrieve) gets intake_count, remove the duplicate
annotate call from list() (but keep "intake_count" in list()'s values() call),
and ensure the annotation uses the same Count call with
filter=Q(project_intakeissue__status=IntakeIssueStatus.PENDING.value) and the
same alias intake_count so field names remain consistent across endpoints.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98c86b5 and 0c9fc4c.

📒 Files selected for processing (1)
  • apps/api/plane/app/views/project/base.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-29T09:17:54.815Z
Learnt from: NarayanBavisetti
Repo: makeplane/plane PR: 7905
File: apps/api/plane/app/views/search/base.py:241-276
Timestamp: 2025-10-29T09:17:54.815Z
Learning: In apps/api/plane/app/views/search/base.py, the `filter_intakes` method uses `Issue.objects` (base manager) instead of `Issue.issue_objects` (custom manager) because the custom manager filters out all intake statuses, which would prevent querying pending and snoozed intake issues.

Applied to files:

  • apps/api/plane/app/views/project/base.py
📚 Learning: 2025-12-23T14:18:32.899Z
Learnt from: dheeru0198
Repo: makeplane/plane PR: 8339
File: apps/api/plane/db/models/api.py:35-35
Timestamp: 2025-12-23T14:18:32.899Z
Learning: Django REST Framework rate limit strings are flexible: only the first character of the time unit matters. Acceptable formats include: "60/s", "60/sec", "60/second" (all equivalent), "60/m", "60/min", "60/minute" (all equivalent), "60/h", "60/hr", "60/hour" (all equivalent), and "60/d", "60/day" (all equivalent). Abbreviations like "min" are valid and do not need to be changed to "minute". Apply this guidance to any Python files in the project that configure DRF throttling rules.

Applied to files:

  • apps/api/plane/app/views/project/base.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: check:lint
  • GitHub Check: check:types
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
apps/api/plane/app/views/project/base.py (2)

7-7: LGTM: Imports are correct.

The new imports (Count, IntakeIssue, IntakeIssueStatus) are all used in the intake_count annotation and are appropriately placed.

Also applies to: 36-36, 38-38


156-160: The related name "project_intakeissue" is correct and the Count aggregation introduces no N+1 query issues.

The IntakeIssue model extends ProjectBaseModel, which uses related_name="project_%(class)s". This automatically generates the reverse accessor as "project_intakeissue" (the model name in lowercase), matching the annotation in your code. The Count aggregation with filter executes as a single annotation on the main queryset without additional queries per result.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
apps/web/core/store/inbox/project-inbox.store.ts (1)

470-474: Consider extracting intake count update logic to a helper method.

The pattern for reading and updating intake_count is repeated across both create and delete operations, and will be even more prevalent when combined with the status transition logic in inbox-issue.store.ts. Consider extracting this to a helper method like:

private updateProjectIntakeCount(projectId: string, delta: number): void {
  const currentCount = this.store.projectRoot.project.projectMap[projectId]?.intake_count ?? 0;
  set(
    this.store.projectRoot.project.projectMap,
    [projectId, "intake_count"],
    Math.max(0, currentCount + delta)
  );
}

Then use it as this.updateProjectIntakeCount(projectId, 1) or this.updateProjectIntakeCount(projectId, -1).

Also applies to: 507-511

apps/web/core/store/inbox/inbox-issue.store.ts (1)

105-121: Consider extracting the intake count update pattern to reduce duplication.

The pattern for updating intake_count appears 6 times across the three status update methods in this file, and additional times in project-inbox.store.ts. This duplication increases maintenance overhead.

♻️ Suggested refactoring approach

Consider adding a private helper method to encapsulate the intake count update logic:

private adjustProjectIntakeCount(delta: 1 | -1): void {
  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 + delta)
  );
}

Then replace the repeated blocks with:

// Decrementing
this.adjustProjectIntakeCount(-1);

// Incrementing
this.adjustProjectIntakeCount(1);

This would reduce the code size and make future modifications to the intake count logic easier to maintain consistently.

Also applies to: 152-160, 187-198

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0c9fc4c and 413fc44.

📒 Files selected for processing (2)
  • apps/web/core/store/inbox/inbox-issue.store.ts
  • apps/web/core/store/inbox/project-inbox.store.ts
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,mts,cts}

📄 CodeRabbit inference engine (.github/instructions/typescript.instructions.md)

**/*.{ts,tsx,mts,cts}: Use const type parameters for more precise literal inference in TypeScript 5.0+
Use the satisfies operator to validate types without widening them
Leverage inferred type predicates to reduce the need for explicit is return types in filter/check functions
Use NoInfer<T> utility to block inference for specific type arguments when they should be determined by other arguments
Utilize narrowing in switch(true) blocks for control flow analysis (TypeScript 5.3+)
Rely on narrowing from direct boolean comparisons for type guards
Trust preserved narrowing in closures when variables aren't modified after the check (TypeScript 5.4+)
Use constant indices to narrow object/array properties (TypeScript 5.5+)
Use standard ECMAScript decorators (Stage 3) instead of legacy experimentalDecorators
Use using declarations for explicit resource management with Disposable pattern instead of manual cleanup (TypeScript 5.2+)
Use with { type: "json" } for import attributes; avoid deprecated assert syntax (TypeScript 5.3/5.8+)
Use import type explicitly when importing types to ensure they are erased during compilation, respecting verbatimModuleSyntax flag
Use .ts, .mts, .cts extensions in import type statements (TypeScript 5.2+)
Use import type { Type } from "mod" with { "resolution-mode": "import" } for specific module resolution contexts (TypeScript 5.3+)
Use new iterator methods (map, filter, etc.) if targeting modern environments (TypeScript 5.6+)
Utilize new Set methods like union, intersection, etc., when available (TypeScript 5.5+)
Use Object.groupBy / Map.groupBy standard methods for grouping instead of external libraries (TypeScript 5.4+)
Use Promise.withResolvers() for creating promises with exposed resolve/reject functions (TypeScript 5.7+)
Use copying array methods (toSorted, toSpliced, with) for immutable array operations (TypeScript 5.2+)
Avoid accessing instance fields via super in classes (TypeScript 5....

Files:

  • apps/web/core/store/inbox/inbox-issue.store.ts
  • apps/web/core/store/inbox/project-inbox.store.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Enable TypeScript strict mode and ensure all files are fully typed

Files:

  • apps/web/core/store/inbox/inbox-issue.store.ts
  • apps/web/core/store/inbox/project-inbox.store.ts
**/*.{js,jsx,ts,tsx,json,css}

📄 CodeRabbit inference engine (AGENTS.md)

Use Prettier with Tailwind plugin for code formatting, run pnpm fix:format

Files:

  • apps/web/core/store/inbox/inbox-issue.store.ts
  • apps/web/core/store/inbox/project-inbox.store.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,jsx,ts,tsx}: Use ESLint with shared config across packages, adhering to max warnings limits per package
Use camelCase for variable and function names, PascalCase for components and types
Use try-catch with proper error types and log errors appropriately

Files:

  • apps/web/core/store/inbox/inbox-issue.store.ts
  • apps/web/core/store/inbox/project-inbox.store.ts
🧠 Learnings (1)
📚 Learning: 2025-06-16T07:23:39.497Z
Learnt from: vamsikrishnamathala
Repo: makeplane/plane PR: 7214
File: web/core/store/issue/helpers/base-issues.store.ts:117-117
Timestamp: 2025-06-16T07:23:39.497Z
Learning: In the updateIssueDates method of BaseIssuesStore (web/core/store/issue/helpers/base-issues.store.ts), the projectId parameter is intentionally made optional to support override implementations in subclasses. The base implementation requires projectId and includes an early return check, but making it optional allows derived classes to override the method with different parameter requirements.

Applied to files:

  • apps/web/core/store/inbox/project-inbox.store.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: check:types
  • GitHub Check: check:lint
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (5)
apps/web/core/store/inbox/project-inbox.store.ts (2)

470-474: LGTM! Intake count incremented correctly for new PENDING issues.

The logic correctly increments intake_count when a new inbox issue is created with PENDING status. The defensive ?? 0 fallback handles cases where the project hasn't been loaded yet or intake_count is undefined.


491-491: LGTM! Intake count decremented correctly with proper safeguards.

The implementation correctly captures the wasPending state before the asynchronous deletion and decrements intake_count only after successful deletion. The use of Math.max(0, currentCount - 1) prevents negative counts.

Also applies to: 507-511

apps/web/core/store/inbox/inbox-issue.store.ts (3)

97-97: LGTM! Bidirectional status transitions handled correctly.

The implementation correctly captures previousStatus before the async update and properly handles both directions of status transitions:

  • Transitioning away from PENDING: decrements intake_count
  • Transitioning to PENDING: increments intake_count

The use of Math.max(0, ...) when decrementing prevents negative counts, and the logic executes only after a successful API response.

Also applies to: 105-122


141-141: LGTM! Duplicate marking correctly decrements intake count.

The implementation correctly captures the wasPending state and decrements intake_count only when the issue was previously PENDING. Since marking as DUPLICATE always transitions away from PENDING (when applicable), the unidirectional decrement logic is appropriate.

Also applies to: 152-160


177-177: LGTM! Snooze transitions handle intake count correctly.

The implementation properly handles both snoozing and unsnoozing:

  • Snoozing (PENDING → SNOOZED): decrements intake_count
  • Unsnoozing (any status → PENDING): increments intake_count if not already PENDING

The previousStatus capture ensures correct state tracking across the async operation.

Also applies to: 187-198

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In @apps/api/plane/app/views/project/base.py:
- Around line 155-163: The intake_count annotation is applied only in list()
using the related_name project_intakeissue, causing inconsistency for
retrieve/list_detail; move the Count annotation (the intake_count calculation
using Count("project_intakeissue",
filter=Q(project_intakeissue__status=IntakeIssueStatus.PENDING.value,
project_intakeissue__deleted_at__isnull=True))) into get_queryset() so all
endpoints that use ProjectListSerializer receive the field consistently;
additionally, add a DB index on IntakeIssue covering (project_id, status,
deleted_at) in the IntakeIssue model/migration to optimize the filtered Count
aggregation.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 413fc44 and ddb8725.

📒 Files selected for processing (1)
  • apps/api/plane/app/views/project/base.py
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-29T09:17:54.815Z
Learnt from: NarayanBavisetti
Repo: makeplane/plane PR: 7905
File: apps/api/plane/app/views/search/base.py:241-276
Timestamp: 2025-10-29T09:17:54.815Z
Learning: In apps/api/plane/app/views/search/base.py, the `filter_intakes` method uses `Issue.objects` (base manager) instead of `Issue.issue_objects` (custom manager) because the custom manager filters out all intake statuses, which would prevent querying pending and snoozed intake issues.

Applied to files:

  • apps/api/plane/app/views/project/base.py
📚 Learning: 2025-12-23T14:18:32.899Z
Learnt from: dheeru0198
Repo: makeplane/plane PR: 8339
File: apps/api/plane/db/models/api.py:35-35
Timestamp: 2025-12-23T14:18:32.899Z
Learning: Django REST Framework rate limit strings are flexible: only the first character of the time unit matters. Acceptable formats include: "60/s", "60/sec", "60/second" (all equivalent), "60/m", "60/min", "60/minute" (all equivalent), "60/h", "60/hr", "60/hour" (all equivalent), and "60/d", "60/day" (all equivalent). Abbreviations like "min" are valid and do not need to be changed to "minute". Apply this guidance to any Python files in the project that configure DRF throttling rules.

Applied to files:

  • apps/api/plane/app/views/project/base.py
🧬 Code graph analysis (1)
apps/api/plane/app/views/project/base.py (1)
apps/api/plane/db/models/intake.py (1)
  • IntakeIssueStatus (38-43)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: check:types
  • GitHub Check: check:lint
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (3)
apps/api/plane/app/views/project/base.py (3)

7-7: LGTM! Import addition is correct.

The Count import is necessary for the intake count aggregation and is correctly placed with other Django ORM imports.


174-174: LGTM! Field projection is correct.

The intake_count field is correctly included in the values() projection to ensure the computed annotation is returned in the API response.


37-37: Import is correct and necessary.

The IntakeIssueStatus enum is properly defined with PENDING = -2, and the model's status field default and choices are aligned. Usage at line 159 correctly applies .value to filter pending intake issues.

Comment on lines +155 to +163
.annotate(
intake_count=Count(
"project_intakeissue",
filter=Q(
project_intakeissue__status=IntakeIssueStatus.PENDING.value,
project_intakeissue__deleted_at__isnull=True,
),
)
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the IntakeIssue model to verify the related_name
fd -t f ".*intake.*\.py" --type f | grep -iE "model|intake" | head -20

Repository: makeplane/plane

Length of output: 487


🏁 Script executed:

#!/bin/bash
# Search for IntakeIssue class definition
rg -l "class IntakeIssue" --type py

Repository: makeplane/plane

Length of output: 329


🏁 Script executed:

#!/bin/bash
# Look at the views file structure to understand methods
cat -n apps/api/plane/app/views/project/base.py | head -250

Repository: makeplane/plane

Length of output: 10640


🏁 Script executed:

#!/bin/bash
# Find the IntakeIssue model definition and check the foreign key to Project
cat -n apps/api/plane/db/models/intake.py | head -150

Repository: makeplane/plane

Length of output: 3137


🏁 Script executed:

#!/bin/bash
# Search for the related_name in the IntakeIssue model
rg "related_name" apps/api/plane/db/models/intake.py -A 2 -B 2

Repository: makeplane/plane

Length of output: 500


🏁 Script executed:

#!/bin/bash
# Check ProjectListSerializer to see if it includes intake_count
rg -A 30 "class ProjectListSerializer" apps/api/plane/app/serializers/

Repository: makeplane/plane

Length of output: 2791


🏁 Script executed:

#!/bin/bash
# Check ProjectBaseModel to see how project FK is defined
rg -A 10 "class ProjectBaseModel" apps/api/plane/db/models/ --type py | head -30

Repository: makeplane/plane

Length of output: 890


🏁 Script executed:

#!/bin/bash
# Search for IntakeIssue's project field or check if it's inherited
rg "project.*IntakeIssue\|IntakeIssue.*project" apps/api/plane/db/models/ --type py -A 2 -B 2

Repository: makeplane/plane

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Check if there are any database migrations that show the related_name
rg "IntakeIssue" apps/api/plane/db/migrations/0085_intake_intakeissue_remove_inboxissue_created_by_and_more.py -A 5 -B 5 | head -100

Repository: makeplane/plane

Length of output: 2070


🏁 Script executed:

#!/bin/bash
# Check database indexes on IntakeIssue table
rg -A 5 "class IntakeIssue" apps/api/plane/db/models/intake.py | grep -A 20 "class Meta"

Repository: makeplane/plane

Length of output: 41


🏁 Script executed:

#!/bin/bash
# Search for any index definitions in migrations or models for IntakeIssue
rg "IntakeIssue.*index\|index.*IntakeIssue" apps/api/plane/db/ --type py

Repository: makeplane/plane

Length of output: 41


🏁 Script executed:

#!/bin_bash
# Check the full serializer definition to see all fields
cat -n apps/api/plane/app/serializers/project.py | grep -A 50 "class ProjectListSerializer"

Repository: makeplane/plane

Length of output: 2420


🏁 Script executed:

#!/bin/bash
# Final check: verify that ProjectBaseModel's related_name pattern works for IntakeIssue
# and that there's no explicit related_name override in IntakeIssue model
grep -n "class IntakeIssue\|related_name\|project" apps/api/plane/db/models/intake.py | head -20

Repository: makeplane/plane

Length of output: 690


🏁 Script executed:

#!/bin/bash
# Check if get_queryset needs the intake_count annotation for consistency
# by seeing if list_detail and retrieve should return it
grep -n "intake_count" apps/api/plane/app/ -r --type py

Repository: makeplane/plane

Length of output: 176


🏁 Script executed:

#!/bin/bash
# Check where intake_count is referenced in the codebase
rg "intake_count" apps/api/plane/ --type py

Repository: makeplane/plane

Length of output: 204


🏁 Script executed:

#!/bin/bash
# Look at the Project model to see if intake_count is a real model field
rg -A 5 "class Project" apps/api/plane/db/models/project.py | head -50

Repository: makeplane/plane

Length of output: 2135


The relationship name is correct, but consider consistency and indexes.

The annotation correctly uses project_intakeissue as the related_name, which is automatically generated by ProjectBaseModel's pattern related_name="project_%(class)s" for the IntakeIssue model.

However, two legitimate concerns remain:

  1. Inconsistency across endpoints: The intake_count annotation is only added to the list() method (lines 155-163) but not to get_queryset(). This means list_detail() and retrieve() won't include intake_count in their responses even though they use the same ProjectListSerializer. If the frontend expects this field consistently, move the annotation to get_queryset().

  2. Missing database indexes: For optimal performance, add indexes on IntakeIssue table for (project_id, status, deleted_at) to support the Count aggregation with filters.

🤖 Prompt for AI Agents
In @apps/api/plane/app/views/project/base.py around lines 155 - 163, The
intake_count annotation is applied only in list() using the related_name
project_intakeissue, causing inconsistency for retrieve/list_detail; move the
Count annotation (the intake_count calculation using
Count("project_intakeissue",
filter=Q(project_intakeissue__status=IntakeIssueStatus.PENDING.value,
project_intakeissue__deleted_at__isnull=True))) into get_queryset() so all
endpoints that use ProjectListSerializer receive the field consistently;
additionally, add a DB index on IntakeIssue covering (project_id, status,
deleted_at) in the IntakeIssue model/migration to optimize the filtered Count
aggregation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants