Skip to content

Conversation

@sangeethailango
Copy link
Member

@sangeethailango sangeethailango commented Nov 6, 2025

Description

  • Added management command to create the description for all the issue comments
  • Added description and parent_id field in IssueComment
  • Added sync logic in the IssueComment module to sync the IssueComment and Description.

Summary by CodeRabbit

  • New Features

    • Issue comments now support threaded replies and maintain a linked rich-text description that stays synchronized on create/update.
  • Tests

    • Added comprehensive unit tests covering creation, updates, legacy-data handling, and HTML stripping for issue comments and their descriptions.
  • Chores

    • Added a migration/utility to backfill existing comments into the new description records.

sync: issue_comment and description
@makeplane
Copy link

makeplane bot commented Nov 6, 2025

Linked to Plane Work Item(s)

This comment was auto-generated by Plane

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 6, 2025

Walkthrough

Adds a Description relation and a self-referential parent field to IssueComment, updates IssueComment.save() to create/update Description records, adds a management command to backfill Description for existing comments, and includes unit tests and a Django migration for the schema changes.

Changes

Cohort / File(s) Summary
Model and Field Definitions
apps/api/plane/db/models/issue.py
Added Description import. Added description OneToOneField to IssueComment (nullable, cascade) and parent self-referential ForeignKey (nullable, blank). Enhanced IssueComment.save() to create a Description on create and update/create associated Description on changes.
Data Migration Command
apps/api/plane/db/management/commands/copy_issue_comment_to_description.py
New management command that batches IssueComment rows (500 each), creates corresponding Description records from comment fields in atomic transactions, bulk-creates them, and updates IssueComment rows with the new description_id.
Database Migration
apps/api/plane/db/migrations/0109_issuecomment_description_and_parent_id.py
Migration adding description OneToOneField and parent ForeignKey to IssueComment. Depends on prior migration 0108_alter_issueactivity_issue_comment.
Unit Tests
apps/api/plane/tests/unit/models/test_issue_comment_modal.py
New tests covering creation (automatic Description creation), updates syncing to Description, legacy cases where Description is missing, HTML stripping behavior, no-op updates, and other edge scenarios.

Sequence Diagram

sequenceDiagram
    participant User
    participant IssueComment
    participant Description
    participant DB

    rect rgba(200,220,255,0.25)
    note over User,IssueComment: Creation flow
    User->>IssueComment: create with comment_html/json/stripped
    IssueComment->>IssueComment: save() detects new
    IssueComment->>Description: build Description from comment fields
    Description->>DB: create Description
    DB-->>Description: return id
    IssueComment->>IssueComment: set description_id
    IssueComment->>DB: save IssueComment
    DB-->>User: return created IssueComment
    end

    rect rgba(220,255,220,0.25)
    note over User,IssueComment: Update flow
    User->>IssueComment: update comment_html/json/stripped
    IssueComment->>IssueComment: save() detects changes
    alt Description exists
        IssueComment->>Description: update fields
        Description->>DB: save
    else Description missing (legacy)
        IssueComment->>Description: create new Description
        Description->>DB: create
        DB-->>Description: return id
        IssueComment->>IssueComment: set description_id
    end
    IssueComment->>DB: save IssueComment
    DB-->>User: return updated IssueComment
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay attention to the IssueComment.save() conditional logic for creation vs update and field-change detection.
  • Review the management command's batching, atomic transactions, field mappings, and the bulk create / bulk update correctness.
  • Verify migration adds fields with correct nullability, relations, and related_names.
  • Inspect tests for coverage of legacy/migration scenarios and HTML stripping edge cases.

Poem

🐰 I dug a tunnel, soft and small,
Where comments nest and threads now sprawl.
Descriptions bloom from every note,
Parents link where echoes float,
Hooray — my burrow's neat and tall! ✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.57% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main change: adding a migration to support replies (parent_id) for work item comments and setting up the description infrastructure, which aligns with the changeset.
Description check ✅ Passed The description covers the main changes (management command, new fields, sync logic) but lacks coverage of required template sections like Type of Change, Test Scenarios, and References.
✨ 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 chore-description-in-issue-comment

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.

@sangeethailango sangeethailango marked this pull request as ready for review November 6, 2025 09:57
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 696fb96 and df965a7.

📒 Files selected for processing (2)
  • apps/api/plane/db/migrations/0109_issuecomment_description.py (1 hunks)
  • apps/api/plane/db/models/issue.py (3 hunks)

@sangeethailango sangeethailango added the 🔄migrations Contains Migration changes label Nov 6, 2025
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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df965a7 and 0987943.

📒 Files selected for processing (1)
  • apps/api/plane/db/models/issue.py (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-23T18:18:06.875Z
Learnt from: NarayanBavisetti
Repo: makeplane/plane PR: 7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.

Applied to files:

  • apps/api/plane/db/models/issue.py
🧬 Code graph analysis (1)
apps/api/plane/db/models/issue.py (1)
apps/api/plane/db/models/page.py (2)
  • save (66-73)
  • save (171-178)
⏰ 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 and lint web apps
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
apps/api/plane/db/models/issue.py (2)

20-20: LGTM!

The import is correctly placed and follows the existing module import pattern.


450-452: Verify the cascade direction.

The on_delete=CASCADE means that deleting a Description will also delete the IssueComment. Typically, the comment should control the description lifecycle (i.e., use on_delete=CASCADE on the description side), not the other way around. Please confirm this is intentional.

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 (1)
apps/api/plane/db/management/commands/copy_issue_comment_to_description.py (1)

20-48: Consider adding progress tracking and error handling.

The batch processing logic is sound, but operational visibility could be improved:

  1. Progress tracking: Show batch number and records processed (e.g., "Processed batch 3/10: 1500/5000 records")
  2. Error handling: Add try-except around bulk operations with appropriate logging
  3. Informative messages: Include counts in success messages

These improvements would make the command more robust and easier to monitor during execution.

Example enhancement for progress tracking:

+        processed = 0
         for i in range(0, total, batch_size):
             with transaction.atomic():
                 comments = list(issue_comments[i : i + batch_size])
+                batch_count = len(comments)

                 descriptions = [
                     Description(
                         created_at=comment.created_at,
                         updated_at=comment.updated_at,
                         description_json=comment.comment_json,
                         description_html=comment.comment_html,
                         description_stripped=comment.comment_stripped,
                         project_id=comment.project_id,
                         created_by_id=comment.created_by_id,
                         updated_by_id=comment.updated_by_id,
                         workspace_id=comment.workspace_id,
                     )
                     for comment in comments
                 ]

                 created_descriptions = Description.objects.bulk_create(descriptions)

                 comments_to_update = []
                 for comment, description in zip(comments, created_descriptions):
                     comment.description_id = description.id
                     comments_to_update.append(comment)

-                IssueComment.objects.bulk_update(comments_to_update, ["description"])
+                IssueComment.objects.bulk_update(comments_to_update, ["description_id"])

+            processed += batch_count
-            self.stdout.write(self.style.SUCCESS("Successfully Copied IssueComment to Description"))
+            self.stdout.write(
+                self.style.SUCCESS(
+                    f"Processed {processed}/{total} comments ({(processed/total)*100:.1f}%)"
+                )
+            )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0987943 and ac85e59.

📒 Files selected for processing (1)
  • apps/api/plane/db/management/commands/copy_issue_comment_to_description.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/api/plane/db/management/commands/copy_issue_comment_to_description.py (1)
apps/api/plane/db/models/issue.py (1)
  • IssueComment (446-509)
⏰ 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 and lint web apps
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (3)
apps/api/plane/db/management/commands/copy_issue_comment_to_description.py (3)

1-7: LGTM!

All necessary imports are present and correctly structured.


10-11: LGTM!

Class definition and help text are appropriate.


13-18: LGTM!

Query filters correctly target non-deleted comments without descriptions, and the batch size of 500 is reasonable for this migration.

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

♻️ Duplicate comments (1)
apps/api/plane/db/models/issue.py (1)

488-491: Assign the new description_id on the instance.

Because description_id is cached before the create call, this branch leaves self.description_id as None. The very next save on the same instance re-enters this block, creating additional Description rows, and any in-memory access to self.description right after save() fails. Please set the field on the instance after updating the DB.

         if description_id:
             Description.objects.filter(pk=self.description_id).update(**description_defaults)
         else:
             description = Description.objects.create(**description_defaults)
-            IssueComment.objects.filter(pk=self.pk).update(description_id=description.id)
+            IssueComment.objects.filter(pk=self.pk).update(description_id=description.id)
+            self.description_id = description.id
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac85e59 and 69120b8.

📒 Files selected for processing (2)
  • apps/api/plane/db/management/commands/copy_issue_comment_to_description.py (1 hunks)
  • apps/api/plane/db/models/issue.py (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-23T18:18:06.875Z
Learnt from: NarayanBavisetti
Repo: makeplane/plane PR: 7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.

Applied to files:

  • apps/api/plane/db/models/issue.py
🧬 Code graph analysis (1)
apps/api/plane/db/management/commands/copy_issue_comment_to_description.py (1)
apps/api/plane/db/models/issue.py (1)
  • IssueComment (446-501)
⏰ 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: Analyze (javascript)
  • GitHub Check: Build and lint web apps

@sangeethailango sangeethailango changed the title [WEB-5350]migration: description field on issue_comment [WEB-5350]migration: Data Migration - reply for work item comments Nov 6, 2025
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/db/management/commands/copy_issue_comment_to_description.py (2)

16-21: Consider adding explicit ordering for deterministic batch processing.

While the IssueComment model has a default ordering of ("-created_at",), explicitly specifying .order_by("id") or .order_by("created_at") in batch processing ensures deterministic results across different database configurations and makes the code's intent clearer.

Apply this diff:

         while True:
             comments = list(
-                IssueComment.objects.filter(deleted_at__isnull=True, description_id__isnull=True)[:batch_size]
+                IssueComment.objects.filter(
+                    deleted_at__isnull=True, description_id__isnull=True
+                ).order_by("id")[:batch_size]
             )
             if not comments:
                 break

13-48: Add progress tracking and final summary for better observability.

The current implementation only logs success after each batch without indicating progress. Consider adding batch counts and a final summary message to help monitor the migration's progress and completion.

Apply this diff:

     def handle(self, *args, **kwargs):
         batch_size = 500
+        total_processed = 0

         while True:
             comments = list(
                 IssueComment.objects.filter(deleted_at__isnull=True, description_id__isnull=True)[:batch_size]
             )
             if not comments:
                 break

             with transaction.atomic():
                 descriptions = [
                     Description(
                         created_at=comment.created_at,
                         updated_at=comment.updated_at,
                         description_json=comment.comment_json,
                         description_html=comment.comment_html,
                         description_stripped=comment.comment_stripped,
                         project_id=comment.project_id,
                         created_by_id=comment.created_by_id,
                         updated_by_id=comment.updated_by_id,
                         workspace_id=comment.workspace_id,
                     )
                     for comment in comments
                 ]

                 created_descriptions = Description.objects.bulk_create(descriptions)

                 comments_to_update = []
                 for comment, description in zip(comments, created_descriptions):
                     comment.description_id = description.id
                     comments_to_update.append(comment)

                 IssueComment.objects.bulk_update(comments_to_update, ["description_id"])

-            self.stdout.write(self.style.SUCCESS("Successfully Copied IssueComment to Description"))
+            total_processed += len(comments)
+            self.stdout.write(
+                self.style.SUCCESS(
+                    f"Processed batch of {len(comments)} comments (total: {total_processed})"
+                )
+            )
+
+        self.stdout.write(
+            self.style.SUCCESS(
+                f"Migration complete. Total IssueComments processed: {total_processed}"
+            )
+        )
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 69120b8 and 8a72d2a.

📒 Files selected for processing (1)
  • apps/api/plane/db/management/commands/copy_issue_comment_to_description.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/api/plane/db/management/commands/copy_issue_comment_to_description.py (1)
apps/api/plane/db/models/issue.py (1)
  • IssueComment (446-501)
⏰ 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 and lint web apps
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
apps/api/plane/db/management/commands/copy_issue_comment_to_description.py (2)

1-8: LGTM!

The imports are appropriate for a Django management command that performs batch operations.


10-11: LGTM!

The command class is properly structured with appropriate help text.

@sangeethailango sangeethailango changed the title [WEB-5350]migration: Data Migration - reply for work item comments migration: Data Migration - reply for work item comments Nov 7, 2025
@sangeethailango sangeethailango changed the title migration: Data Migration - reply for work item comments [WEB-5312] migration: Data Migration - reply for work item comments Nov 7, 2025
@sangeethailango sangeethailango changed the title [WEB-5312] migration: Data Migration - reply for work item comments [WEB-5312] migration: reply for work item comments Nov 7, 2025
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/db/management/commands/copy_issue_comment_to_description.py (2)

17-19: Add explicit ordering for determinism.

The queryset lacks an ORDER BY clause. While the filter on description_id__isnull=True ensures each batch is distinct, an explicit order improves consistency and debuggability across database engines.

Apply this diff to add ordering:

             comments = list(
-                IssueComment.objects.filter(deleted_at__isnull=True, description_id__isnull=True)[:batch_size]
+                IssueComment.objects.filter(
+                    deleted_at__isnull=True, description_id__isnull=True
+                ).order_by("id")[:batch_size]
             )

48-48: Consider adding progress messages.

For large datasets, printing progress after each batch would improve observability during migration execution.

Example enhancement:

            IssueComment.objects.bulk_update(comments_to_update, ["description_id"])
            self.stdout.write(f"Processed {len(comments)} comments...")

    self.stdout.write(self.style.SUCCESS("Successfully Copied IssueComment to Description"))
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8a72d2a and ea1e95c.

📒 Files selected for processing (1)
  • apps/api/plane/db/management/commands/copy_issue_comment_to_description.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/api/plane/db/management/commands/copy_issue_comment_to_description.py (1)
apps/api/plane/db/models/issue.py (1)
  • IssueComment (446-501)
⏰ 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: Analyze (python)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Build and lint web apps
🔇 Additional comments (1)
apps/api/plane/db/management/commands/copy_issue_comment_to_description.py (1)

1-11: LGTM!

Imports and command setup are clean and appropriate.

chore: test cases
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 (1)
apps/api/plane/db/models/issue.py (1)

471-478: Consider consistency with existing patterns for empty descriptions.

The code sets comment_stripped = "" when comment_html is empty (line 472), but other models in the codebase (e.g., Page.save() at apps/api/plane/db/models/page.py:65-72) set description_stripped = None for empty HTML:

self.description_stripped = (
    None
    if (self.description_html == "" or self.description_html is None)
    else strip_tags(self.description_html)
)

This inconsistency may cause unexpected behavior. Consider aligning with the established pattern.

Minor: Use self.pk instead of self.id on line 476.

Line 476 uses self.id when fetching the old comment. While functionally equivalent, self.pk is the more canonical Django pattern and should be preferred for consistency.

Note: The early super().save() call (line 478) correctly addresses the timing issues flagged in previous reviews.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea1e95c and f18e120.

📒 Files selected for processing (2)
  • apps/api/plane/db/models/issue.py (3 hunks)
  • apps/api/plane/tests/unit/models/test_issue_comment_modal.py (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-23T18:18:06.875Z
Learnt from: NarayanBavisetti
Repo: makeplane/plane PR: 7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.

Applied to files:

  • apps/api/plane/db/models/issue.py
🧬 Code graph analysis (2)
apps/api/plane/db/models/issue.py (1)
apps/api/plane/db/models/page.py (2)
  • save (66-73)
  • save (171-178)
apps/api/plane/tests/unit/models/test_issue_comment_modal.py (4)
apps/api/plane/db/models/issue.py (4)
  • IssueComment (446-522)
  • Issue (105-251)
  • save (179-247)
  • save (471-512)
apps/api/plane/db/models/project.py (1)
  • Project (65-159)
apps/api/plane/db/models/workspace.py (1)
  • Workspace (115-178)
apps/api/plane/tests/conftest.py (1)
  • create_user (33-42)
⏰ 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 and lint web apps
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (6)
apps/api/plane/db/models/issue.py (1)

450-452: LGTM: Description field definition is correct.

The OneToOneField definition properly establishes the relationship with appropriate cascade behavior and null support for legacy data.

apps/api/plane/tests/unit/models/test_issue_comment_modal.py (5)

6-48: LGTM: Test fixtures are well-structured.

The fixtures properly set up the test environment with appropriate dependencies and use the existing create_user fixture from conftest.


54-85: LGTM: Comprehensive test coverage for creation flow.

The test properly verifies that Description is automatically created and all fields are synchronized correctly during IssueComment creation.


86-196: LGTM: Excellent coverage of update scenarios.

These tests comprehensively verify the synchronization behavior for updates, including:

  • Full updates (HTML + JSON)
  • Partial updates (only HTML changed)
  • No-op scenarios (no changes)

The tests align well with the change detection logic implemented in the model's save() method.


197-264: LGTM: Strong coverage for edge cases.

The legacy data test properly simulates the scenario where Description is missing and verifies it's created on update. The HTML stripping test ensures proper text extraction from HTML tags.


265-289: Test expectation is correct—no issue found.

The test at line 289 correctly expects description.description_stripped == None. When IssueComment.save() creates a Description with empty description_html, the Description model's own save() method (apps/api/plane/db/models/description.py:18-23) explicitly converts empty descriptions to None:

self.description_stripped = (
    None
    if (self.description_html == "" or self.description_html is None)
    else strip_tags(self.description_html)
)

Since description_html="" is passed to the Description, its save() method sets description_stripped=None, aligning with the test expectation and matching the pattern used in other models (Page, PageVersion, Sticky, Draft).

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/db/management/commands/copy_issue_comment_to_description.py (2)

13-23: Excellent fix to the batch processing logic!

The while-True loop correctly pulls fresh batches until none remain, which resolves the previous issue where rows were being skipped. The ordering by created_at and absence of deleted_at filter align with the reviewer's feedback and the model's save() behavior.

Optional enhancement: Add progress reporting.

For better observability during migration, consider logging the batch progress:

         while True:
             comments = list(
                 IssueComment.objects.filter(description_id__isnull=True).order_by("created_at")[:batch_size]
             )
 
             if not comments:
                 break
+
+            self.stdout.write(f"Processing batch of {len(comments)} comments...")
 
             with transaction.atomic():

24-48: Correct field mapping and bulk operations!

The Description field mappings are consistent with the IssueComment.save() logic, and the bulk_update correctly uses "description_id" (addressing the previous issue). The transaction.atomic() ensures each batch is processed atomically.

Optional enhancement: Add error handling for robustness.

While transaction rollback provides safety, explicit error handling could improve observability:

             with transaction.atomic():
+                try:
                     descriptions = [
                         Description(
                             created_at=comment.created_at,
                             updated_at=comment.updated_at,
                             description_json=comment.comment_json,
                             description_html=comment.comment_html,
                             description_stripped=comment.comment_stripped,
                             project_id=comment.project_id,
                             created_by_id=comment.created_by_id,
                             updated_by_id=comment.updated_by_id,
                             workspace_id=comment.workspace_id,
                         )
                         for comment in comments
                     ]
 
                     created_descriptions = Description.objects.bulk_create(descriptions)
 
                     comments_to_update = []
                     for comment, description in zip(comments, created_descriptions):
                         comment.description_id = description.id
                         comments_to_update.append(comment)
 
                     IssueComment.objects.bulk_update(comments_to_update, ["description_id"])
+                except Exception as e:
+                    self.stdout.write(self.style.ERROR(f"Error processing batch: {e}"))
+                    raise
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f18e120 and 24f8010.

📒 Files selected for processing (1)
  • apps/api/plane/db/management/commands/copy_issue_comment_to_description.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/api/plane/db/management/commands/copy_issue_comment_to_description.py (1)
apps/api/plane/db/models/issue.py (1)
  • IssueComment (446-522)
⏰ 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 and lint web apps
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (2)
apps/api/plane/db/management/commands/copy_issue_comment_to_description.py (2)

1-11: LGTM! Clean setup.

The imports and command class definition are appropriate and well-structured.


49-49: LGTM!

Clear success message confirms completion.

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/migrations/0109_issuecomment_description_and_parent_id.py (1)

14-18: Consider SET_NULL instead of CASCADE for the description field.

The on_delete=CASCADE means that if a Description record is accidentally deleted, the IssueComment will also be deleted. Since IssueComment semantically "owns" the Description (not the reverse), consider using on_delete=models.SET_NULL to preserve the comment if the Description is deleted, or on_delete=models.PROTECT to prevent orphaning.

Apply this diff if you want to use SET_NULL:

 migrations.AddField(
     model_name='issuecomment',
     name='description',
-    field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='issue_comment_description', to='db.description'),
+    field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='issue_comment_description', to='db.description'),
 ),

Can you verify whether Description records might be deleted independently, and whether IssueComment should survive such deletions?

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

478-481: Minor: Race condition in change detection.

Fetching old_issue_comment at line 479 before super().save() creates a small race window where another process could update the comment between the fetch and save, causing your change detection to compare against stale data. For most use cases this is acceptable, but for strict consistency you could store original values in __init__ or use Django's built-in field tracking (e.g., django-model-utils FieldTracker).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0daf524 and c65d2ff.

📒 Files selected for processing (2)
  • apps/api/plane/db/migrations/0109_issuecomment_description_and_parent_id.py (1 hunks)
  • apps/api/plane/db/models/issue.py (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-23T18:18:06.875Z
Learnt from: NarayanBavisetti
Repo: makeplane/plane PR: 7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.

Applied to files:

  • apps/api/plane/db/models/issue.py
🧬 Code graph analysis (1)
apps/api/plane/db/models/issue.py (2)
apps/api/plane/db/models/page.py (2)
  • save (66-73)
  • save (171-178)
apps/api/plane/db/models/cycle.py (1)
  • save (84-93)
⏰ 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 and lint web apps
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (3)
apps/api/plane/db/migrations/0109_issuecomment_description_and_parent_id.py (1)

19-23: Verify CASCADE deletion for nested comment replies.

The on_delete=models.CASCADE for the self-referential parent field means deleting a parent comment will recursively delete all child replies. If this is intentional (nested comment threads should be removed together), this is correct. However, if replies should be preserved when a parent is deleted (perhaps re-parenting them or making them top-level), consider SET_NULL instead.

Can you confirm that deleting a parent comment should also delete all its replies?

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

499-509: Excellent: Change detection addresses previous feedback.

The implementation now properly detects which fields have changed and only updates the Description when necessary. This directly addresses the feedback from dheeru0198 about avoiding unnecessary syncs.

Based on learnings.


493-497: LGTM: Correct creation and update order.

Creating the Description after super().save() ensures timestamps are populated, and using update_fields=["description_id"] on the second save prevents infinite recursion. This properly addresses previous timing issues.

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 (1)
apps/api/plane/db/models/issue.py (1)

450-452: Verify the on_delete direction for the description field.

The on_delete=models.CASCADE on the description field means that if a Description record is deleted, the IssueComment will also be deleted. Since the save() method treats Description as a dependent entity managed by IssueComment, this direction seems counterintuitive. If the Description is accidentally deleted, the entire comment would be lost.

Consider using on_delete=models.PROTECT or on_delete=models.SET_NULL to prevent the IssueComment from being deleted if the Description is removed, or verify that CASCADE is the intended behavior given the architecture.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c65d2ff and 6095f86.

📒 Files selected for processing (1)
  • apps/api/plane/db/models/issue.py (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-07-23T18:18:06.875Z
Learnt from: NarayanBavisetti
Repo: makeplane/plane PR: 7460
File: apps/api/plane/app/serializers/draft.py:112-122
Timestamp: 2025-07-23T18:18:06.875Z
Learning: In the Plane codebase serializers, workspace_id is not consistently passed in serializer context, so parent issue validation in DraftIssueCreateSerializer only checks project_id rather than both workspace_id and project_id. The existing project member authentication system already validates that users can only access projects they belong to, providing sufficient security without risking breaking functionality by adding workspace_id validation where the context might not be available.

Applied to files:

  • apps/api/plane/db/models/issue.py
🧬 Code graph analysis (1)
apps/api/plane/db/models/issue.py (4)
apps/api/plane/db/models/page.py (2)
  • save (66-73)
  • save (171-178)
apps/api/plane/db/models/cycle.py (1)
  • save (84-93)
apps/api/plane/db/models/view.py (1)
  • save (74-90)
apps/api/plane/db/models/module.py (1)
  • save (111-120)

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

Labels

🔄migrations Contains Migration changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants