Skip to content

Conversation

@sriramveeraghanta
Copy link
Member

@sriramveeraghanta sriramveeraghanta commented Nov 28, 2025

Summary by CodeRabbit

  • Refactor
    • Centralized state management and default state definitions across the app for consistent behavior.
    • Consolidated triage handling so state comparisons and creations use the unified group-based definitions.
  • Bug Fixes
    • Standardized triage naming (now "Triage") and updated related messages for clearer end-user feedback.

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

Copilot AI review requested due to automatic review settings November 28, 2025 11:26
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 28, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Introduces a StateGroup TextChoices enum and DEFAULT_STATES, updates the State model to use StateGroup for the group field, and replaces scattered uses of the old State.* triage constant with StateGroup.TRIAGE.value across serializers, views, managers, models, and tasks.

Changes

Cohort / File(s) Summary
Core state definitions
apps/api/plane/db/models/state.py
Add StateGroup (TextChoices) and DEFAULT_STATES. Change State.group to use StateGroup.choices with default StateGroup.BACKLOG. Update StateManager and TriageStateManager querysets to reference StateGroup.TRIAGE.value. Remove prior inline state constants/GROUP_CHOICES.
Public exports
apps/api/plane/db/models/__init__.py
Export StateGroup and DEFAULT_STATES in addition to State.
API serializers
apps/api/plane/api/serializers/intake.py, apps/api/plane/api/serializers/state.py
Import StateGroup and replace checks formerly using State.TRIAGE with StateGroup.TRIAGE.value in validation/update logic.
API views
apps/api/plane/api/views/intake.py, apps/api/plane/api/views/project.py
Import StateGroup and/or DEFAULT_STATES. Use StateGroup.TRIAGE.value when creating triage states and iterate DEFAULT_STATES for bulk state creation (replace hardcoded list).
App serializers
apps/api/plane/app/serializers/intake.py, apps/api/plane/app/serializers/state.py
Import StateGroup and replace State.TRIAGE references with StateGroup.TRIAGE.value in validation checks.
App views
apps/api/plane/app/views/intake/base.py, apps/api/plane/app/views/project/base.py
Import StateGroup and/or DEFAULT_STATES. Use StateGroup.TRIAGE.value for triage state creation and DEFAULT_STATES for bulk creation (name changed from "Intake Triage" to "Triage" in some places).
Issue model query
apps/api/plane/db/models/issue.py
Import StateGroup and update exclusion/filtering of triage states to use StateGroup.TRIAGE.value.
Background tasks
apps/api/plane/bgtasks/dummy_data_task.py
Import StateGroup and replace State.TRIAGE with StateGroup.TRIAGE.value in state filtering logic.
Space views
apps/api/plane/space/views/intake.py
Import StateGroup and use StateGroup.TRIAGE.value and name "Triage" for triage state creation.
Minor message update
apps/api/plane/app/views/state/base.py
Update error message text from "Intake triage state not found" to "Triage state not found".

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Focus areas:
    • apps/api/plane/db/models/state.py — new enum, DEFAULT_STATES and manager/queryset changes; verify model migrations and behavior.
    • Consistency checks across all files using StateGroup.TRIAGE.value (search-and-replace style changes but critical to correctness).
    • DEFAULT_STATES usage in bulk-creation sites (project endpoints) and triage state naming differences.

Poem

🐰 I hopped through code with nimble paws,
Replaced old constants with tidy laws,
Enums in rows, defaults in place,
Triage found its proper space,
A tiny hop — a tidier base.

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request has no description provided. The template requires sections for Description, Type of Change, Test Scenarios, and References, but none were filled out. Add a detailed description explaining the refactoring, mark the appropriate change type (likely Code refactoring), document test scenarios, and link any related issues.
Docstring Coverage ⚠️ Warning Docstring coverage is 29.41% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: state group choices' is specific and clearly relates to the main changes in the PR, which refactor state group handling by introducing a StateGroup enum.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-state-group-choices

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4653d5 and cfce3e7.

📒 Files selected for processing (4)
  • apps/api/plane/api/views/intake.py (2 hunks)
  • apps/api/plane/app/views/intake/base.py (2 hunks)
  • apps/api/plane/app/views/state/base.py (1 hunks)
  • apps/api/plane/space/views/intake.py (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/api/plane/space/views/intake.py
  • apps/api/plane/api/views/intake.py
  • apps/api/plane/app/views/intake/base.py
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: NarayanBavisetti
Repo: makeplane/plane PR: 8135
File: apps/api/plane/db/migrations/0109_auto_20251113_1112.py:24-36
Timestamp: 2025-11-20T11:00:42.330Z
Learning: In the State model, filtering triage states is handled by the StateManager (State.objects) which excludes group="triage". Setting group="triage" is sufficient for triage states; the is_triage field is not required to be set to True for proper filtering behavior.
📚 Learning: 2025-11-20T11:00:42.330Z
Learnt from: NarayanBavisetti
Repo: makeplane/plane PR: 8135
File: apps/api/plane/db/migrations/0109_auto_20251113_1112.py:24-36
Timestamp: 2025-11-20T11:00:42.330Z
Learning: In the State model, filtering triage states is handled by the StateManager (State.objects) which excludes group="triage". Setting group="triage" is sufficient for triage states; the is_triage field is not required to be set to True for proper filtering behavior.

Applied to files:

  • apps/api/plane/app/views/state/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). (1)
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (1)
apps/api/plane/app/views/state/base.py (1)

138-138: LGTM: Error message updated for consistency.

The error message has been updated from "Intake triage state not found" to "Triage state not found", which aligns with the PR's goal of standardizing triage state terminology across the codebase.

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors state group management by introducing a StateGroup enum (Django TextChoices) to replace hardcoded string constants and consolidating default state definitions into a single DEFAULT_STATES constant. This improves type safety, reduces code duplication, and aligns with Django best practices for handling database choices.

Key changes:

  • Introduced StateGroup enum class to centralize state group definitions
  • Created DEFAULT_STATES constant to eliminate duplicated state definitions across project creation views
  • Updated all references from State.TRIAGE, State.BACKLOG, etc. to StateGroup.TRIAGE.value, StateGroup.BACKLOG.value

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
apps/api/plane/db/models/state.py Introduces StateGroup enum, DEFAULT_STATES constant, and updates State model to use enum for choices
apps/api/plane/db/models/init.py Exports StateGroup and DEFAULT_STATES for use across the codebase
apps/api/plane/db/models/issue.py Updates issue filtering to use StateGroup.TRIAGE.value instead of State.TRIAGE
apps/api/plane/space/views/intake.py Updates triage state creation to use StateGroup.TRIAGE.value
apps/api/plane/bgtasks/dummy_data_task.py Updates state filtering logic to use StateGroup.TRIAGE.value
apps/api/plane/app/views/project/base.py Replaces inline state definitions with DEFAULT_STATES constant
apps/api/plane/app/views/intake/base.py Updates triage state creation to use StateGroup.TRIAGE.value
apps/api/plane/app/serializers/state.py Updates validation to use StateGroup.TRIAGE.value
apps/api/plane/app/serializers/intake.py Updates state group checks to use StateGroup.TRIAGE.value and removes redundant import
apps/api/plane/api/views/project.py Replaces inline state definitions with DEFAULT_STATES constant
apps/api/plane/api/views/intake.py Updates triage state creation to use StateGroup.TRIAGE.value
apps/api/plane/api/serializers/state.py Updates validation to use StateGroup.TRIAGE.value
apps/api/plane/api/serializers/intake.py Updates state group checks to use StateGroup.TRIAGE.value and removes redundant import

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

),
default="backlog",
choices=StateGroup.choices,
default=StateGroup.BACKLOG,
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

The default parameter should be set to StateGroup.BACKLOG.value (a string) instead of StateGroup.BACKLOG (the enum member). The group field is a CharField that stores string values, not enum objects. This will cause a type error when Django tries to use the default value.

Change to:

default=StateGroup.BACKLOG.value,
Suggested change
default=StateGroup.BACKLOG,
default=StateGroup.BACKLOG.value,

Copilot uses AI. Check for mistakes.
"group": StateGroup.CANCELLED.value,
},
{
"name": "Triage",
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

The triage state name "Triage" in DEFAULT_STATES is inconsistent with "Intake Triage" used when dynamically creating triage states in intake views (e.g., apps/api/plane/api/views/intake.py:173, apps/api/plane/app/views/intake/base.py:238, apps/api/plane/space/views/intake.py:131).

This inconsistency means:

  1. Projects created with DEFAULT_STATES will have a state named "Triage"
  2. Projects that dynamically create triage states through intake views will have a state named "Intake Triage"

Consider either:

  • Changing line 53 to "name": "Intake Triage", to match the dynamic creation
  • Or updating all intake views to use "Triage" consistently
Suggested change
"name": "Triage",
"name": "Intake Triage",

Copilot uses AI. Check for mistakes.
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

🧹 Nitpick comments (2)
apps/api/plane/db/models/issue.py (1)

100-101: Consider removing redundant triage filter.

Both Line 100 (.filter(state__is_triage=False)) and Line 101 (.exclude(state__group=StateGroup.TRIAGE.value)) are filtering out triage states. Based on learnings, setting group="triage" is sufficient for triage states, making the is_triage field filter potentially redundant.

Consider removing Line 100 to simplify the filtering logic:

         .filter(deleted_at__isnull=True)
-        .filter(state__is_triage=False)
         .exclude(state__group=StateGroup.TRIAGE.value)
         .exclude(archived_at__isnull=False)
apps/api/plane/db/models/state.py (1)

19-58: Based on my verification, I need to rewrite the review comment. The original concern about duplicates is technically incorrect (the code prevents them), but there is a valid design concern about having Triage in DEFAULT_STATES.

Remove Triage from DEFAULT_STATES to align with on-demand intake creation pattern.

Triage states are created on-demand when intake is enabled (via apps/api/plane/api/views/intake.py line 169-179), with proper checks to prevent duplicates. Including Triage in DEFAULT_STATES means all projects get a triage state at creation, even those that never use intake—contradicting the on-demand pattern.

While State.objects properly excludes triage via the StateManager (excluding group="triage"), the redundant default Triage state wastes minimal resources. For cleaner design, remove it from DEFAULT_STATES:

     {
         "name": "Cancelled",
         "color": "#9AA4BC",
         "sequence": 55000,
         "group": StateGroup.CANCELLED.value,
     },
-    {
-        "name": "Triage",
-        "color": "#4E5355",
-        "sequence": 65000,
-        "group": StateGroup.TRIAGE.value,
-    },
 ]
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2980836 and d4653d5.

📒 Files selected for processing (13)
  • apps/api/plane/api/serializers/intake.py (3 hunks)
  • apps/api/plane/api/serializers/state.py (2 hunks)
  • apps/api/plane/api/views/intake.py (2 hunks)
  • apps/api/plane/api/views/project.py (2 hunks)
  • apps/api/plane/app/serializers/intake.py (3 hunks)
  • apps/api/plane/app/serializers/state.py (2 hunks)
  • apps/api/plane/app/views/intake/base.py (2 hunks)
  • apps/api/plane/app/views/project/base.py (2 hunks)
  • apps/api/plane/bgtasks/dummy_data_task.py (2 hunks)
  • apps/api/plane/db/models/__init__.py (1 hunks)
  • apps/api/plane/db/models/issue.py (2 hunks)
  • apps/api/plane/db/models/state.py (1 hunks)
  • apps/api/plane/space/views/intake.py (2 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: NarayanBavisetti
Repo: makeplane/plane PR: 8135
File: apps/api/plane/db/migrations/0109_auto_20251113_1112.py:24-36
Timestamp: 2025-11-20T11:00:42.330Z
Learning: In the State model, filtering triage states is handled by the StateManager (State.objects) which excludes group="triage". Setting group="triage" is sufficient for triage states; the is_triage field is not required to be set to True for proper filtering behavior.
📚 Learning: 2025-11-20T11:00:42.330Z
Learnt from: NarayanBavisetti
Repo: makeplane/plane PR: 8135
File: apps/api/plane/db/migrations/0109_auto_20251113_1112.py:24-36
Timestamp: 2025-11-20T11:00:42.330Z
Learning: In the State model, filtering triage states is handled by the StateManager (State.objects) which excludes group="triage". Setting group="triage" is sufficient for triage states; the is_triage field is not required to be set to True for proper filtering behavior.

Applied to files:

  • apps/api/plane/app/views/intake/base.py
  • apps/api/plane/app/serializers/intake.py
  • apps/api/plane/api/views/intake.py
  • apps/api/plane/db/models/issue.py
  • apps/api/plane/bgtasks/dummy_data_task.py
  • apps/api/plane/api/serializers/state.py
  • apps/api/plane/space/views/intake.py
  • apps/api/plane/app/serializers/state.py
  • apps/api/plane/db/models/state.py
  • apps/api/plane/api/serializers/intake.py
📚 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/serializers/intake.py
  • apps/api/plane/api/views/intake.py
  • apps/api/plane/db/models/issue.py
  • apps/api/plane/space/views/intake.py
  • apps/api/plane/api/serializers/intake.py
📚 Learning: 2025-11-24T07:44:08.690Z
Learnt from: NarayanBavisetti
Repo: makeplane/plane PR: 8135
File: apps/api/plane/db/migrations/0112_auto_20251124_0603.py:18-24
Timestamp: 2025-11-24T07:44:08.690Z
Learning: In Django data migrations, `apps.get_model()` returns historical models that use the base `models.Manager()` (unfiltered) by default. Custom managers defined on the model (like `StateManager` that filters records) are NOT applied in migrations unless the manager class explicitly sets `use_in_migrations = True`. Therefore, when checking migration code that uses `apps.get_model("db", "State")`, the queries using `State.objects` will use the unfiltered base manager, not any custom filtering managers defined on the State model in the codebase.

Applied to files:

  • apps/api/plane/db/models/issue.py
  • apps/api/plane/db/models/state.py
🧬 Code graph analysis (12)
apps/api/plane/app/views/intake/base.py (1)
apps/api/plane/db/models/state.py (1)
  • StateGroup (10-16)
apps/api/plane/api/views/project.py (1)
apps/api/plane/tests/unit/models/test_issue_comment_modal.py (1)
  • state (28-35)
apps/api/plane/app/views/project/base.py (1)
apps/api/plane/tests/unit/models/test_issue_comment_modal.py (1)
  • state (28-35)
apps/api/plane/api/views/intake.py (1)
apps/api/plane/db/models/state.py (2)
  • State (75-122)
  • StateGroup (10-16)
apps/api/plane/db/models/issue.py (2)
apps/api/plane/tests/unit/models/test_issue_comment_modal.py (1)
  • state (28-35)
apps/api/plane/db/models/state.py (1)
  • StateGroup (10-16)
apps/api/plane/bgtasks/dummy_data_task.py (1)
apps/api/plane/db/models/state.py (2)
  • StateGroup (10-16)
  • State (75-122)
apps/api/plane/api/serializers/state.py (1)
apps/api/plane/db/models/state.py (2)
  • State (75-122)
  • StateGroup (10-16)
apps/api/plane/space/views/intake.py (1)
apps/api/plane/db/models/state.py (2)
  • State (75-122)
  • StateGroup (10-16)
apps/api/plane/db/models/__init__.py (1)
apps/api/plane/db/models/state.py (2)
  • State (75-122)
  • StateGroup (10-16)
apps/api/plane/app/serializers/state.py (1)
apps/api/plane/db/models/state.py (2)
  • State (75-122)
  • StateGroup (10-16)
apps/api/plane/db/models/state.py (1)
apps/api/plane/db/models/issue.py (1)
  • get_queryset (89-105)
apps/api/plane/api/serializers/intake.py (1)
apps/api/plane/db/models/state.py (2)
  • State (75-122)
  • StateGroup (10-16)
⏰ 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: Agent
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (24)
apps/api/plane/db/models/issue.py (1)

22-22: LGTM!

The import of StateGroup is correctly added to support the refactored state group handling.

apps/api/plane/api/serializers/intake.py (2)

4-4: LGTM!

The imports are correctly updated to include both State and StateGroup from plane.db.models.


118-118: LGTM!

The triage state checks are correctly updated to use StateGroup.TRIAGE.value instead of State.TRIAGE, aligning with the centralized state group definitions.

Also applies to: 142-142

apps/api/plane/api/serializers/state.py (2)

3-3: LGTM!

The import is correctly updated to include StateGroup alongside State.


20-21: LGTM!

The validation logic correctly uses StateGroup.TRIAGE.value for the triage state check, ensuring consistency with the new enum-based approach.

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

34-34: LGTM!

The import of DEFAULT_STATES enables centralized default state definitions, improving maintainability.


268-282: DEFAULT_STATES structure is correct and matches the expected format.

Verification confirms that DEFAULT_STATES (defined in apps/api/plane/db/models/state.py lines 20-50) contains all required keys (name, color, sequence, group) across all state dictionaries. The optional default key is present only in the "Backlog" state and is safely handled via state.get("default", False) in the code, which defaults to False when absent. The bulk_create operation will execute without key-related errors.

apps/api/plane/bgtasks/dummy_data_task.py (2)

20-20: LGTM!

The import of StateGroup is correctly added to support the refactored state group filtering.


267-271: LGTM!

The filter logic correctly uses StateGroup.TRIAGE.value to exclude triage states when creating dummy issues, ensuring consistency with the new enum-based approach.

apps/api/plane/app/views/intake/base.py (2)

25-25: LGTM!

The import of StateGroup is correctly added to support the refactored triage state creation.


237-245: LGTM!

The triage state creation correctly uses StateGroup.TRIAGE.value for the group parameter, ensuring consistency with the new enum-based state grouping.

apps/api/plane/space/views/intake.py (2)

15-15: LGTM!

The import is correctly updated to include StateGroup alongside other necessary models.


130-138: LGTM!

The triage state creation correctly uses StateGroup.TRIAGE.value for the group parameter, maintaining consistency with the refactored state group handling across the codebase.

apps/api/plane/app/serializers/state.py (2)

5-5: LGTM!

The import is correctly updated to include both State and StateGroup from plane.db.models.


28-29: LGTM!

The validation logic correctly uses StateGroup.TRIAGE.value for the triage state check, ensuring consistency with the centralized state group definitions.

apps/api/plane/app/serializers/intake.py (2)

10-10: LGTM!

The import of StateGroup alongside State is correct and aligns with the PR's goal to centralize state group definitions.


51-51: Consistent migration to StateGroup enum.

The change from State.TRIAGE to StateGroup.TRIAGE.value correctly uses the new centralized enum for triage state checks. This ensures consistency with the StateManager filtering logic that uses StateGroup.TRIAGE.value.

Also applies to: 71-71

apps/api/plane/api/views/intake.py (2)

26-26: LGTM!

Adding StateGroup to the imports is correct and necessary for the triage state creation below.


169-180: Correct usage of StateGroup for triage state creation.

The change from a hardcoded value to StateGroup.TRIAGE.value ensures consistency with the TriageStateManager which filters by the same enum value. Based on learnings, setting group="triage" is sufficient for triage states since the StateManager excludes this group by default.

apps/api/plane/api/views/project.py (2)

27-27: LGTM!

Importing DEFAULT_STATES centralizes state definitions and reduces code duplication across project creation flows.


236-250: I'll help you verify this review comment about triage state creation in the default project states. Let me start by examining the relevant code sections.
<function_calls>

#!/bin/bash

Find and examine DEFAULT_STATES definition

rg -n "DEFAULT_STATES" --type=py -A 20 | head -60


</function_calls>
<function_calls>

#!/bin/bash

Examine the intake view to see how triage states are created

rg -n "triage_objects" --type=py -B 3 -A 10 | head -80


</function_calls>
<function_calls>

#!/bin/bash

Check the project.py file to understand the context better

rg -n "group.*triage" --type=py -B 2 -A 2


</function_calls>
<function_calls>

#!/bin/bash

Look for the State model definition and how it handles triage states

rg -n "class State" --type=py -A 15 | head -100


</function_calls>

Please wait for the results of these searches to verify the concern about potential duplicate triage states.

apps/api/plane/db/models/__init__.py (1)

59-59: LGTM!

Exporting StateGroup and DEFAULT_STATES from the models package enables clean imports across the codebase (from plane.db.models import StateGroup, DEFAULT_STATES).

apps/api/plane/db/models/state.py (2)

10-17: Well-structured StateGroup enum.

The TextChoices enum provides type safety and centralized definitions for state groups. This replaces scattered hardcoded strings throughout the codebase.


61-72: LGTM!

Both managers correctly use StateGroup.TRIAGE.value for filtering, ensuring consistency with the new enum-based approach.

Comment on lines 81 to 85
group = models.CharField(
choices=(
("backlog", "Backlog"),
("unstarted", "Unstarted"),
("started", "Started"),
("completed", "Completed"),
("cancelled", "Cancelled"),
("triage", "Triage"),
),
default="backlog",
choices=StateGroup.choices,
default=StateGroup.BACKLOG,
max_length=20,
)
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:

web_search
Django TextChoices CharField default enum member vs value behavior

Repository: makeplane/plane

Length of output: 154


🏁 Script executed:

# Find the StateGroup definition in the codebase
rg -n "class StateGroup" apps/api/plane/db/models/ -A 10

Repository: makeplane/plane

Length of output: 766


🏁 Script executed:

# Search for other uses of StateGroup default values
rg -n "default.*StateGroup" apps/api/plane/db/models/ -B 2 -A 2

Repository: makeplane/plane

Length of output: 374


🏁 Script executed:

# Check the full state.py file to see the context around line 81-85
sed -n '75,90p' apps/api/plane/db/models/state.py

Repository: makeplane/plane

Length of output: 802


🏁 Script executed:

# Search for Django TextChoices documentation or examples in the codebase
rg -n "TextChoices" apps/api/ -B 2 -A 5 | head -50

Repository: makeplane/plane

Length of output: 3188


🏁 Script executed:

# Search for all default= patterns with Choices/TextChoices in the codebase
rg -n "default\s*=\s*\w+\." apps/api/plane/db/models/ -B 2 -A 1 | grep -E "(default|models\.(TextChoices|IntegerChoices|Choices))" | head -40

Repository: makeplane/plane

Length of output: 3308


🏁 Script executed:

# Check Django version to understand TextChoices behavior
find apps/api -name "requirements*.txt" -o -name "pyproject.toml" -o -name "setup.py" | xargs grep -i django 2>/dev/null | head -5

Repository: makeplane/plane

Length of output: 41


🏁 Script executed:

# Create a simple Python test to verify TextChoices behavior
python3 << 'EOF'
import sys
sys.path.insert(0, '/tmp')

# Simulate Django TextChoices behavior
class TextChoices(str):
    """Simplified version of Django's TextChoices"""
    def __new__(cls, value, label):
        obj = str.__new__(cls, value)
        obj._value_ = value
        obj._label_ = label
        return obj

class StateGroup(TextChoices):
    BACKLOG = "backlog", "Backlog"
    STARTED = "started", "Started"

# Test the behavior
print(f"StateGroup.BACKLOG type: {type(StateGroup.BACKLOG)}")
print(f"StateGroup.BACKLOG value: {repr(StateGroup.BACKLOG)}")
print(f"StateGroup.BACKLOG == 'backlog': {StateGroup.BACKLOG == 'backlog'}")
print(f"str(StateGroup.BACKLOG): {str(StateGroup.BACKLOG)}")
print(f"StateGroup.BACKLOG is a string: {isinstance(StateGroup.BACKLOG, str)}")

# Check if .value attribute exists
try:
    print(f"StateGroup.BACKLOG.value: {StateGroup.BACKLOG.value}")
except AttributeError as e:
    print(f"StateGroup.BACKLOG.value error: {e}")
EOF

Repository: makeplane/plane

Length of output: 145


🏁 Script executed:

# Check if there are any migrations or model usages that show the pattern
rg -n "StateGroup\." apps/api/plane/db/models/state.py -A 1 -B 1 | head -20

Repository: makeplane/plane

Length of output: 527


🏁 Script executed:

# Search Django usage in the project to understand version
find . -name "*.py" -path "*/apps/api/*" -type f | xargs grep -l "django" | head -3 | xargs grep "from django" | head -10

Repository: makeplane/plane

Length of output: 334


🏁 Script executed:

# Get the full DEFAULT_STATES context
sed -n '19,60p' apps/api/plane/db/models/state.py

Repository: makeplane/plane

Length of output: 955


🏁 Script executed:

# Check if there are any Django model tests or documentation comments
rg -n "TextChoices|CharField" apps/api/plane/db/models/state.py -B 2 -A 3

Repository: makeplane/plane

Length of output: 1094


🏁 Script executed:

# Search for other model field defaults using TextChoices to see the pattern
rg -n "default\s*=\s*\w+\.\w+" apps/api/plane/db/models/ -B 3 -A 1 | grep -A 3 -B 3 "Choices\|choices"

Repository: makeplane/plane

Length of output: 1884


🏁 Script executed:

# Search for any Django documentation or comments about TextChoices usage
rg -n "TextChoices|text.*choices" apps/api/plane/ --type=py -B 2 -A 5 -i | head -60

Repository: makeplane/plane

Length of output: 4009


🏁 Script executed:

# Check if there's any documentation or comments in the Django model files
head -30 apps/api/plane/db/models/state.py

Repository: makeplane/plane

Length of output: 766


🏁 Script executed:

# Create a verification to understand Django TextChoices inheritance
python3 << 'EOF'
# Demonstrate Django TextChoices behavior
# TextChoices inherits from str, so the enum member IS the string value

class TextChoicesMember(str):
    """Mimics Django TextChoices member behavior"""
    def __new__(cls, value, label):
        obj = str.__new__(cls, value)
        obj._value_ = value
        obj._label_ = label
        return obj

# Simulate StateGroup
class StateGroup:
    BACKLOG = TextChoicesMember("backlog", "Backlog")

# Check equivalence
member = StateGroup.BACKLOG
print(f"member: {repr(member)}")
print(f"member.value: {repr(member.value)}")
print(f"member == 'backlog': {member == 'backlog'}")
print(f"member == member.value: {member == member.value}")
print(f"They are identical: {member is member.value}")
EOF

Repository: makeplane/plane

Length of output: 261


🏁 Script executed:

# Search for Django's Enum or TextChoices usage patterns in test files
find apps/api -name "*test*" -type f -name "*.py" | xargs grep -l "TextChoices\|StateGroup" 2>/dev/null | head -3

Repository: makeplane/plane

Length of output: 41


🏁 Script executed:

# Check if there are any migration files that show the pattern
find apps/api -path "*/migrations/*.py" -type f | xargs grep -l "StateGroup" 2>/dev/null | head -2

Repository: makeplane/plane

Length of output: 41


🏁 Script executed:

# Let's verify what .value actually returns for Django's TextChoices
python3 << 'EOF'
# Django's TextChoices inherits from str and Enum
# Let's check the actual behavior
from enum import Enum

class TextChoices(str, Enum):
    """Closer to Django's TextChoices"""
    pass

class StateGroup(TextChoices):
    BACKLOG = "backlog"
    STARTED = "started"

# Check behavior
member = StateGroup.BACKLOG
print(f"member type: {type(member)}")
print(f"member value: {repr(member)}")
print(f"member == 'backlog': {member == 'backlog'}")
print(f"hasattr value: {hasattr(member, 'value')}")
print(f"member.value: {repr(member.value)}")
print(f"member == member.value: {member == member.value}")
print(f"str(member): {str(member)}")
EOF

Repository: makeplane/plane

Length of output: 268


Perfect! I now have all the information needed to verify the review comment.

Django's TextChoices correctly handles both default=StateGroup.BACKLOG and default=StateGroup.BACKLOG.value — however, using .value is more explicit and aligns with the codebase pattern.

The verification shows:

  • Django's TextChoices (which inherits from both str and Enum) has a .value attribute
  • StateGroup.BACKLOG and StateGroup.BACKLOG.value are functionally equivalent
  • However, the same file uses .value consistently in DEFAULT_STATES (lines 25, 32, 38, 44, 50)
  • Using .value follows Python Enum conventions and is more explicit

Use default=StateGroup.BACKLOG.value for consistency with the DEFAULT_STATES data structure and to explicitly reference the string value.

While Django accepts default=StateGroup.BACKLOG due to TextChoices inheriting from str, the codebase itself uses .value in the DEFAULT_STATES list (lines 25, 32, 38, 44, 50 in the same file), creating an inconsistency. Explicitly accessing .value follows Python Enum conventions and makes the intent clearer:

    group = models.CharField(
        choices=StateGroup.choices,
-       default=StateGroup.BACKLOG,
+       default=StateGroup.BACKLOG.value,
        max_length=20,
    )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
group = models.CharField(
choices=(
("backlog", "Backlog"),
("unstarted", "Unstarted"),
("started", "Started"),
("completed", "Completed"),
("cancelled", "Cancelled"),
("triage", "Triage"),
),
default="backlog",
choices=StateGroup.choices,
default=StateGroup.BACKLOG,
max_length=20,
)
group = models.CharField(
choices=StateGroup.choices,
default=StateGroup.BACKLOG.value,
max_length=20,
)
🤖 Prompt for AI Agents
In apps/api/plane/db/models/state.py around lines 81-85, the field default
currently uses StateGroup.BACKLOG (a TextChoices member) which is functionally
valid but inconsistent with the file's use of Enum .value; change the default to
StateGroup.BACKLOG.value so the CharField explicitly receives the string value
and matches the DEFAULT_STATES entries for consistency.

@pushya22 pushya22 merged commit c7bf912 into preview Nov 28, 2025
6 of 7 checks passed
@pushya22 pushya22 deleted the fix-state-group-choices branch November 28, 2025 12:36
ClarenceChen0627 pushed a commit to ClarenceChen0627/plane that referenced this pull request Dec 5, 2025
ClarenceChen0627 pushed a commit to ClarenceChen0627/plane that referenced this pull request Dec 5, 2025
ClarenceChen0627 pushed a commit to ClarenceChen0627/plane that referenced this pull request Dec 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants