Skip to content

[WEB-3987] chore: project export funtionality enhancement#7002

Merged
sriramveeraghanta merged 12 commits intopreviewfrom
chore-project-export-func-enhancement
May 9, 2025
Merged

[WEB-3987] chore: project export funtionality enhancement#7002
sriramveeraghanta merged 12 commits intopreviewfrom
chore-project-export-func-enhancement

Conversation

@sangeethailango
Copy link
Member

@sangeethailango sangeethailango commented May 1, 2025

Description

This PR will add more fields to the project export and refactor the code.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

Summary by CodeRabbit

  • New Features
    • Exported issue data now includes additional fields such as comments, assignees, links, estimate, subscriber count, and attachment information.
  • Refactor
    • Improved data formatting for exported issues, providing more detailed and structured information in CSV, JSON, and XLSX exports.

Reference
WEB-2790

@coderabbitai
Copy link
Contributor

coderabbitai bot commented May 1, 2025

"""

Walkthrough

The export task for issues was refactored to fetch complete model instances with related objects using Django's ORM, manually constructing enriched dictionaries with nested and related data for each issue. Export row generation functions and headers were updated to accommodate the new data structure, and additional related fields were included in all export formats.

Changes

File(s) Change Summary
apiserver/plane/bgtasks/export_task.py Refactored issue data retrieval to use full model instances with related data; constructed enriched dictionaries; updated export row functions and headers; included new related fields (comments, assignees, links, attachments, etc.); added necessary imports.

Suggested labels

🧹chore, 🌟improvement, 🛠️refactor

Suggested reviewers

  • sriramveeraghanta

Poem

In the warren's code, we dig anew,
Fetching issues, their friends, and their crew.
Now with comments and links,
And attachments in sync,
Our exports are richer—hip-hop, bunny-hop, woohoo!
🐇📦✨
"""

✨ 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@sangeethailango sangeethailango changed the title chore: comment details of work item chore: project export funtionality May 1, 2025
@sangeethailango sangeethailango changed the title chore: project export funtionality chore: project export funtionality enhancement May 2, 2025
@sangeethailango sangeethailango changed the title chore: project export funtionality enhancement [WEB-3987] chore: project export funtionality enhancement May 2, 2025
@makeplane
Copy link

makeplane bot commented May 2, 2025

Pull Request Linked with Plane Work Items

Comment Automatically Generated by Plane

@sangeethailango sangeethailango self-assigned this May 6, 2025
@sangeethailango sangeethailango requested a review from dheeru0198 May 6, 2025 09:17
@sangeethailango sangeethailango marked this pull request as ready for review May 6, 2025 09:17
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: 2

🧹 Nitpick comments (5)
apiserver/plane/bgtasks/export_task.py (5)

18-24: Remove unused imports

Several imported modules are not used in the code, making it harder to understand dependencies and potentially increasing load time.

-from django.db.models import Value
-from django.db.models.functions import Coalesce
-from django.db.models import Q
-from django.contrib.postgres.aggregates import ArrayAgg
-from django.db.models import OuterRef, Func, F, Count
-from django.db.models.functions import Concat
-from django.db import models
🧰 Tools
🪛 Ruff (0.8.2)

18-18: django.db.models.Value imported but unused

Remove unused import: django.db.models.Value

(F401)


19-19: django.db.models.functions.Coalesce imported but unused

Remove unused import: django.db.models.functions.Coalesce

(F401)


20-20: django.db.models.Q imported but unused

Remove unused import: django.db.models.Q

(F401)


21-21: django.contrib.postgres.aggregates.ArrayAgg imported but unused

Remove unused import: django.contrib.postgres.aggregates.ArrayAgg

(F401)


22-22: django.db.models.OuterRef imported but unused

Remove unused import

(F401)


22-22: django.db.models.Func imported but unused

Remove unused import

(F401)


22-22: django.db.models.F imported but unused

Remove unused import

(F401)


22-22: django.db.models.Count imported but unused

Remove unused import

(F401)


23-23: django.db.models.functions.Concat imported but unused

Remove unused import: django.db.models.functions.Concat

(F401)


24-24: django.db.models imported but unused

Remove unused import: django.db.models

(F401)


162-195: Enhancement to include rich information in exports

The table row generation has been updated to include additional fields like comments, estimate, links, assignees, and attachment information, providing a more comprehensive export.

However, you should use the .get() method for all dictionary keys that might not be present to avoid potential KeyError exceptions:

-        issue["description"],
+        issue.get("description", ""),
-        issue["state_name"],
+        issue.get("state_name", ""),
🧰 Tools
🪛 Ruff (0.8.2)

184-184: Line too long (92 > 88)

(E501)


300-306: Remove debug print statements

Debug print statements should be removed before deploying to production.

-        print(issue, "Issue from xlsx")
         row = generate_table_row(issue)
-        print(row, "Row")

369-369: Long line exceeds recommended length

This line exceeds the recommended length of 88 characters.

-                "created_by": f"{issue.created_by.first_name} {issue.created_by.last_name}",
+                "created_by": (
+                    f"{issue.created_by.first_name} {issue.created_by.last_name}"
+                ),
🧰 Tools
🪛 Ruff (0.8.2)

369-369: Line too long (92 > 88)

(E501)


379-379: Long line exceeds recommended length

This line exceeds the recommended length of 88 characters.

-                        "created_by": f"{comment.created_by.first_name} {comment.created_by.last_name}",
+                        "created_by": (
+                            f"{comment.created_by.first_name} {comment.created_by.last_name}"
+                        ),
🧰 Tools
🪛 Ruff (0.8.2)

379-379: Line too long (104 > 88)

(E501)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 28f9733 and e25eea0.

📒 Files selected for processing (1)
  • apiserver/plane/bgtasks/export_task.py (6 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apiserver/plane/bgtasks/export_task.py (2)
apiserver/plane/db/models/issue.py (1)
  • Issue (104-253)
apiserver/plane/db/models/asset.py (2)
  • FileAsset (24-104)
  • EntityTypeContext (29-39)
🪛 Ruff (0.8.2)
apiserver/plane/bgtasks/export_task.py

18-18: django.db.models.Value imported but unused

Remove unused import: django.db.models.Value

(F401)


19-19: django.db.models.functions.Coalesce imported but unused

Remove unused import: django.db.models.functions.Coalesce

(F401)


20-20: django.db.models.Q imported but unused

Remove unused import: django.db.models.Q

(F401)


21-21: django.contrib.postgres.aggregates.ArrayAgg imported but unused

Remove unused import: django.contrib.postgres.aggregates.ArrayAgg

(F401)


22-22: django.db.models.OuterRef imported but unused

Remove unused import

(F401)


22-22: django.db.models.Func imported but unused

Remove unused import

(F401)


22-22: django.db.models.F imported but unused

Remove unused import

(F401)


22-22: django.db.models.Count imported but unused

Remove unused import

(F401)


23-23: django.db.models.functions.Concat imported but unused

Remove unused import: django.db.models.functions.Concat

(F401)


24-24: django.db.models imported but unused

Remove unused import: django.db.models

(F401)


184-184: Line too long (92 > 88)

(E501)


369-369: Line too long (92 > 88)

(E501)


379-379: Line too long (104 > 88)

(E501)

🔇 Additional comments (5)
apiserver/plane/bgtasks/export_task.py (5)

28-28: LGTM: Appropriate model imports added

The imports have been updated to include Issue and FileAsset models that are now being used in the export functionality.


198-225: Enhanced JSON export with additional fields

The JSON row generation now includes comprehensive information about comments, estimates, links, subscribers, and attachments.


318-343: Improved query optimization with select_related and prefetch_related

Using select_related and prefetch_related is an excellent optimization that reduces the number of database queries needed when fetching related objects.


345-422: LGTM: Comprehensive issue data construction

The code now creates a rich data structure with all relevant issue information, including related entities like labels, comments, assignees, and attachments.

🧰 Tools
🪛 Ruff (0.8.2)

369-369: Line too long (92 > 88)

(E501)


379-379: Line too long (104 > 88)

(E501)


424-450: Export headers updated to match new data structure

Headers have been appropriately updated to include all the new fields that are being exported.

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

♻️ Duplicate comments (2)
apiserver/plane/bgtasks/export_task.py (2)

461-461: ⚠️ Potential issue

Fix filter operation on list object.

There's a critical error in this line. issues_data is a list, not a queryset, so it doesn't have a .filter() method.

-                issues = issues_data.filter(project__id=project_id)
+                issues = [issue for issue in issues_data if issue["project_id"] == project_id]

399-409: 🛠️ Refactor suggestion

Optimize duplicate FileAsset queries.

You're making two separate queries to FileAsset for the same issue - one for count and one for the URL list. This could be optimized to improve performance.

-                "attachment_count": FileAsset.objects.filter(
-                    issue_id=issue.id,
-                    entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
-                ).count(),
-                "attachment_links": [
-                    f"/api/assets/v2/workspaces/{issue.workspace.slug}/projects/{issue.project_id}/issues/{issue.id}/attachments/{asset.id}/"
-                    for asset in FileAsset.objects.filter(
-                        issue_id=issue.id,
-                        entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
-                    )
-                ],
+                # Get attachments in a single query
+                attachments = list(FileAsset.objects.filter(
+                    issue_id=issue.id,
+                    entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
+                )),
+                "attachment_count": len(attachments),
+                "attachment_links": [
+                    f"/api/assets/v2/workspaces/{issue.workspace.slug}/projects/{issue.project_id}/issues/{issue.id}/attachments/{asset.id}/"
+                    for asset in attachments
+                ],
🧹 Nitpick comments (3)
apiserver/plane/bgtasks/export_task.py (3)

18-24: Remove unused imports.

Several imported modules are not used in the codebase according to static analysis:

  • Value from django.db.models
  • Coalesce from django.db.models.functions
  • Q from django.db.models
  • ArrayAgg from django.contrib.postgres.aggregates
  • OuterRef, Func, F, Count from django.db.models
  • Concat from django.db.models.functions
  • models from django.db
-from django.db.models import Value
-from django.db.models.functions import Coalesce
-from django.db.models import Q
-from django.contrib.postgres.aggregates import ArrayAgg
-from django.db.models import OuterRef, Func, F, Count
-from django.db.models.functions import Concat
-from django.db import models
🧰 Tools
🪛 Ruff (0.8.2)

18-18: django.db.models.Value imported but unused

Remove unused import: django.db.models.Value

(F401)


19-19: django.db.models.functions.Coalesce imported but unused

Remove unused import: django.db.models.functions.Coalesce

(F401)


20-20: django.db.models.Q imported but unused

Remove unused import: django.db.models.Q

(F401)


21-21: django.contrib.postgres.aggregates.ArrayAgg imported but unused

Remove unused import: django.contrib.postgres.aggregates.ArrayAgg

(F401)


22-22: django.db.models.OuterRef imported but unused

Remove unused import

(F401)


22-22: django.db.models.Func imported but unused

Remove unused import

(F401)


22-22: django.db.models.F imported but unused

Remove unused import

(F401)


22-22: django.db.models.Count imported but unused

Remove unused import

(F401)


23-23: django.db.models.functions.Concat imported but unused

Remove unused import: django.db.models.functions.Concat

(F401)


24-24: django.db.models imported but unused

Remove unused import: django.db.models

(F401)


368-368: Line length exceeds style guidelines.

These lines exceed the recommended maximum length of 88 characters. Consider breaking them into multiple lines for better readability.

-                "created_by": f"{issue.created_by.first_name} {issue.created_by.last_name}",
+                "created_by": (
+                    f"{issue.created_by.first_name} {issue.created_by.last_name}"
+                ),

-                        "created_by": f"{comment.created_by.first_name} {comment.created_by.last_name}",
+                        "created_by": (
+                            f"{comment.created_by.first_name} {comment.created_by.last_name}"
+                        ),

Also applies to: 378-378

🧰 Tools
🪛 Ruff (0.8.2)

368-368: Line too long (92 > 88)

(E501)


374-383: Consider prefetching comment related data.

Instead of iterating through comments with .all().distinct() within the loop, it would be more efficient to optimize the prefetch operation to include comment authors.

.prefetch_related(
    "labels",
    "issue_cycle__cycle",
    "issue_module__module",
-   "issue_comments",
+   models.Prefetch(
+       "issue_comments",
+       queryset=IssueComment.objects.select_related("created_by").distinct()
+   ),
    "assignees",
    "issue_subscribers",
    "issue_link",
)
🧰 Tools
🪛 Ruff (0.8.2)

378-378: Line too long (104 > 88)

(E501)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e25eea0 and cbc3fc1.

📒 Files selected for processing (1)
  • apiserver/plane/bgtasks/export_task.py (6 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
apiserver/plane/bgtasks/export_task.py

18-18: django.db.models.Value imported but unused

Remove unused import: django.db.models.Value

(F401)


19-19: django.db.models.functions.Coalesce imported but unused

Remove unused import: django.db.models.functions.Coalesce

(F401)


20-20: django.db.models.Q imported but unused

Remove unused import: django.db.models.Q

(F401)


21-21: django.contrib.postgres.aggregates.ArrayAgg imported but unused

Remove unused import: django.contrib.postgres.aggregates.ArrayAgg

(F401)


22-22: django.db.models.OuterRef imported but unused

Remove unused import

(F401)


22-22: django.db.models.Func imported but unused

Remove unused import

(F401)


22-22: django.db.models.F imported but unused

Remove unused import

(F401)


22-22: django.db.models.Count imported but unused

Remove unused import

(F401)


23-23: django.db.models.functions.Concat imported but unused

Remove unused import: django.db.models.functions.Concat

(F401)


24-24: django.db.models imported but unused

Remove unused import: django.db.models

(F401)


184-184: Line too long (92 > 88)

(E501)


368-368: Line too long (92 > 88)

(E501)


378-378: Line too long (104 > 88)

(E501)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Analyze (python)
🔇 Additional comments (4)
apiserver/plane/bgtasks/export_task.py (4)

346-422: Well structured data enrichment with related objects.

The refactoring to use select_related and prefetch_related to fetch complete model instances and build enriched dictionaries is a good approach for including comprehensive data in exports. This provides more flexibility in handling related data compared to using .values().

🧰 Tools
🪛 Ruff (0.8.2)

368-368: Line too long (92 > 88)

(E501)


378-378: Line too long (104 > 88)

(E501)


443-449: CSV header extended with additional fields.

Good job extending the header to include the new fields you're exporting. This will ensure all data appears correctly in the export.


182-192: Good implementation of complex comment formatting.

The string formatting for the comments is well implemented, including the comment text, creation date, and author information.

🧰 Tools
🪛 Ruff (0.8.2)

184-184: Line too long (92 > 88)

(E501)


469-469: Non-multiple export now uses enriched issues_data.

The implementation correctly uses the enriched issues_data list for the single export case, ensuring consistent data structure across all export formats.

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: 3

♻️ Duplicate comments (1)
apiserver/plane/bgtasks/export_task.py (1)

399-409: 🛠️ Refactor suggestion

Optimize attachment queries to avoid duplicating database calls

You're making two separate queries to FileAsset for the same issue - one for count and one for the URL list. This could be optimized by fetching the attachments once and reusing them.

-                "attachment_count": FileAsset.objects.filter(
-                    issue_id=issue.id,
-                    entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
-                ).count(),
-                "attachment_links": [
-                    f"/api/assets/v2/workspaces/{issue.workspace.slug}/projects/{issue.project_id}/issues/{issue.id}/attachments/{asset.id}/"
-                    for asset in FileAsset.objects.filter(
-                        issue_id=issue.id,
-                        entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
-                    )
-                ],
+                # Get attachments in a single query
+                attachments = list(FileAsset.objects.filter(
+                    issue_id=issue.id,
+                    entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
+                )),
+                "attachment_count": len(attachments),
+                "attachment_links": [
+                    f"/api/assets/v2/workspaces/{issue.workspace.slug}/projects/{issue.project_id}/issues/{issue.id}/attachments/{asset.id}/"
+                    for asset in attachments
+                ],
🧹 Nitpick comments (2)
apiserver/plane/bgtasks/export_task.py (2)

184-184: Line exceeds recommended length limit

This line and a few others exceed the 88 character limit typically recommended for Python code.

Consider breaking up the long string formatting into multiple lines:

-                f"{comment['comment']} ({comment['created_at']} by {comment['created_by']})"
+                (f"{comment['comment']} "
+                 f"({comment['created_at']} by {comment['created_by']})")
🧰 Tools
🪛 Ruff (0.8.2)

184-184: Line too long (92 > 88)

(E501)


183-189: Consider adding comment formatting options for better readability

The current implementation concatenates all comments into a single string, which might become unwieldy for issues with many comments.

Consider limiting the number of comments or their length in exports:

        ", ".join(
            [
                f"{comment['comment']} ({comment['created_at']} by {comment['created_by']})"
                for comment in issue["comments"]
+               # Limit to the first 3 comments or similar
+               [:3]
            ]
        )
        if issue["comments"]
        else "",
🧰 Tools
🪛 Ruff (0.8.2)

184-184: Line too long (92 > 88)

(E501)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cbc3fc1 and 66d7c06.

📒 Files selected for processing (1)
  • apiserver/plane/bgtasks/export_task.py (6 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apiserver/plane/bgtasks/export_task.py (2)
apiserver/plane/db/models/issue.py (1)
  • Issue (104-253)
apiserver/plane/db/models/asset.py (2)
  • FileAsset (24-104)
  • EntityTypeContext (29-39)
🪛 Ruff (0.8.2)
apiserver/plane/bgtasks/export_task.py

18-18: django.db.models.Value imported but unused

Remove unused import: django.db.models.Value

(F401)


19-19: django.db.models.functions.Coalesce imported but unused

Remove unused import: django.db.models.functions.Coalesce

(F401)


20-20: django.db.models.Q imported but unused

Remove unused import: django.db.models.Q

(F401)


21-21: django.contrib.postgres.aggregates.ArrayAgg imported but unused

Remove unused import: django.contrib.postgres.aggregates.ArrayAgg

(F401)


22-22: django.db.models.OuterRef imported but unused

Remove unused import

(F401)


22-22: django.db.models.Func imported but unused

Remove unused import

(F401)


22-22: django.db.models.F imported but unused

Remove unused import

(F401)


22-22: django.db.models.Count imported but unused

Remove unused import

(F401)


23-23: django.db.models.functions.Concat imported but unused

Remove unused import: django.db.models.functions.Concat

(F401)


24-24: django.db.models imported but unused

Remove unused import: django.db.models

(F401)


184-184: Line too long (92 > 88)

(E501)


368-368: Line too long (92 > 88)

(E501)


378-378: Line too long (104 > 88)

(E501)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Analyze (python)
🔇 Additional comments (3)
apiserver/plane/bgtasks/export_task.py (3)

325-341: Good use of select_related and prefetch_related for performance

The use of select_related and prefetch_related is a good optimization to reduce database queries by fetching related objects in a minimal number of queries.


344-421: Improved data enrichment for exports with comprehensive issue details

The new approach of building enriched issue dictionaries with related data significantly improves the export functionality by including more context in exports (comments, assignees, attachments, etc.).

🧰 Tools
🪛 Ruff (0.8.2)

368-368: Line too long (92 > 88)

(E501)


378-378: Line too long (104 > 88)

(E501)


423-449: Comprehensive header additions for improved exports

The addition of new fields to the export headers is well-aligned with the enriched data structure, providing more complete and useful exports for users.

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

♻️ Duplicate comments (2)
apiserver/plane/bgtasks/export_task.py (2)

413-421: ⚠️ Potential issue

Cycle information is overwritten for issues with multiple cycles.

The current implementation will only retain information from the last cycle in the loop. If an issue can belong to multiple cycles (as suggested by the model relationships), this implementation will discard all but the last cycle's information.

This is the same issue identified in a previous review. Consider changing to accumulate all cycles in a list:

-            # Get prefetched cycles and modules
-            cycles = list(issue.issue_cycle.all())
-
-            # Update cycle data
-            for cycle in cycles:
-                issue_data["cycle_name"] = cycle.cycle.name
-                issue_data["cycle_start_date"] = dateConverter(cycle.cycle.start_date)
-                issue_data["cycle_end_date"] = dateConverter(cycle.cycle.end_date)
+            # Get prefetched cycles
+            cycles = list(issue.issue_cycle.all())
+            
+            if cycles:
+                # Either accumulate all cycles in a list
+                issue_data["cycles"] = [
+                    {
+                        "cycle_name": cycle.cycle.name,
+                        "cycle_start_date": dateConverter(cycle.cycle.start_date),
+                        "cycle_end_date": dateConverter(cycle.cycle.end_date),
+                    }
+                    for cycle in cycles
+                ]
+                # Or just use the first/latest cycle if only one should be shown
+                latest_cycle = cycles[0]  # or use sorted(cycles, key=lambda x: x.cycle.start_date)[-1] for latest
+                issue_data["cycle_name"] = latest_cycle.cycle.name
+                issue_data["cycle_start_date"] = dateConverter(latest_cycle.cycle.start_date)
+                issue_data["cycle_end_date"] = dateConverter(latest_cycle.cycle.end_date)

461-466: 💡 Verification agent

🧩 Analysis chain

Changed filtering approach on issues data.

The filtering of issues by project ID now uses a list comprehension instead of a queryset filter, as the data is now a list of dictionaries rather than a queryset.

While this works, it could be more efficient to group the issues by project ID in a single pass, especially for large datasets:


🏁 Script executed:

#!/bin/bash
# Check if there are often many projects and issues in the system
echo "Checking number of projects and issues in the system..."
rg -n "class Project" -A 3 -B 3
rg -n "Issue.objects" | wc -l

Length of output: 42624


🏁 Script executed:

#!/bin/bash
# Show context around issues_data usage in export_task.py
rg -n 'issues_data' -A 5 -B 5 apiserver/plane/bgtasks/export_task.py

Length of output: 1684


🏁 Script executed:

#!/bin/bash
# Locate assignment of project_ids in export_task.py
rg -n 'project_ids' -A 3 -B 3 apiserver/plane/bgtasks/export_task.py

Length of output: 926


Group issues by project in one pass for better performance

Instead of filtering the full issues_data list for each project_id, build a lookup once and reuse it:

--- a/apiserver/plane/bgtasks/export_task.py
+++ b/apiserver/plane/bgtasks/export_task.py
@@ 458,14 +458,23 @@ def issue_export_task(provider, workspace_id, project_ids, token_id, multiple, slug):
         files = []
         if multiple:
-            for project_id in project_ids:
-                issues = [
-                    issue
-                    for issue in issues_data
-                    if str(issue["project_id"]) == str(project_id)
-                ]
-
-                exporter = EXPORTER_MAPPER.get(provider)
-                if exporter is not None:
-                    exporter(header, project_id, issues, files)
+            # build a mapping of project_id → list of issues
+            from collections import defaultdict
+            issues_by_project = defaultdict(list)
+            for issue in issues_data:
+                pid = str(issue["project_id"])
+                issues_by_project[pid].append(issue)
+
+            for project_id in project_ids:
+                issues = issues_by_project.get(str(project_id), [])
+                exporter = EXPORTER_MAPPER.get(provider)
+                if exporter:
+                    exporter(header, project_id, issues, files)
 
         else:
             exporter = EXPORTER_MAPPER.get(provider)
             if exporter is not None:
                 exporter(header, workspace_id, issues_data, files)

This reduces the work from O(n × m) to O(n + m), yielding significant speedups when exporting many projects or issues.

🧹 Nitpick comments (1)
apiserver/plane/bgtasks/export_task.py (1)

179-179: Long lines exceed recommended line length.

Several lines exceed the recommended line length of 88 characters, which may reduce readability:

  • Line 179: f-string for comment formatting
  • Line 375: String concatenation for created_by
  • Line 385: String concatenation for comment's created_by

Consider breaking these into multiple lines or using shorter variable names.

Also applies to: 375-375, 385-385

🧰 Tools
🪛 Ruff (0.8.2)

179-179: Line too long (92 > 88)

(E501)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66d7c06 and a695a8b.

📒 Files selected for processing (1)
  • apiserver/plane/bgtasks/export_task.py (7 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apiserver/plane/bgtasks/export_task.py (2)
apiserver/plane/db/models/issue.py (1)
  • Issue (104-253)
apiserver/plane/db/models/asset.py (1)
  • FileAsset (24-104)
🪛 Ruff (0.8.2)
apiserver/plane/bgtasks/export_task.py

179-179: Line too long (92 > 88)

(E501)


375-375: Line too long (92 > 88)

(E501)


385-385: Line too long (104 > 88)

(E501)

⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Analyze (javascript)
🔇 Additional comments (9)
apiserver/plane/bgtasks/export_task.py (9)

18-20: Appropriate imports added for the refactoring.

The imports for django.db.models.F and collections.defaultdict are correctly added to support the new data processing approach. The defaultdict is particularly useful for efficiently grouping attachments by issue ID.


23-23: Properly added imports for the required models.

The FileAsset model is now explicitly imported to support the new attachment handling functionality.


157-191: Enhanced data export with comprehensive related information.

The generate_table_row function has been updated to include additional valuable information in the export:

  • Comments with their text, creation date, and author
  • Issue estimates
  • Links associated with the issue
  • Assignees listed by name
  • Subscriber count
  • Attachment count and links

This provides a more complete picture of each issue in the exported data.

🧰 Tools
🪛 Ruff (0.8.2)

179-179: Line too long (92 > 88)

(E501)


194-221: JSON export enhanced with comprehensive issue data.

Similar to the table row generation, the JSON export now includes the full set of issue information, providing a more complete data export.


314-339: Improved query efficiency with select_related and prefetch_related.

The refactored code now properly uses Django's select_related and prefetch_related to efficiently fetch related data in a single query, rather than triggering additional queries when accessing related objects. This is a significant optimization that reduces database load.


341-348: Efficient batch processing of file attachments.

Instead of querying file assets for each issue individually, the code now fetches all relevant assets in a single query and organizes them by issue ID using a defaultdict. This approach significantly reduces the number of database queries.


349-411: Comprehensive issue data preparation with related objects.

The code now constructs a rich dictionary for each issue, including related data like labels, comments, links, assignees, and attachments. This approach provides more complete information in the exports while maintaining good query performance through prefetching.

🧰 Tools
🪛 Ruff (0.8.2)

375-375: Line too long (92 > 88)

(E501)


385-385: Line too long (104 > 88)

(E501)


444-450: Export headers updated to include additional data.

The export headers have been appropriately updated to include all the new fields, ensuring that the exports contain complete information.


475-475: Updated exporter function call to use processed data.

The exporter function call correctly uses the processed issues_data list instead of the raw queryset.

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: 2

♻️ Duplicate comments (1)
apiserver/plane/bgtasks/export_task.py (1)

413-421: ⚠️ Potential issue

Multiple cycles are not being captured properly.

This code still overwrites cycle data in each iteration, causing only the last cycle to be retained. Since an issue can belong to multiple cycles (based on unique_together=["issue", "cycle", "deleted_at"]), all cycles should be captured.

Apply this change to accumulate all cycles in a list instead:

-            # Get prefetched cycles and modules
-            cycles = list(issue.issue_cycle.all())
-
-            # Update cycle data
-            for cycle in cycles:
-                issue_data["cycle_name"] = cycle.cycle.name
-                issue_data["cycle_start_date"] = dateConverter(cycle.cycle.start_date)
-                issue_data["cycle_end_date"] = dateConverter(cycle.cycle.end_date)
+            # Get prefetched cycles as a list of dictionaries
+            cycles = list(issue.issue_cycle.all())
+            if cycles:
+                issue_data["cycles"] = [
+                    {
+                        "cycle_name": ci.cycle.name,
+                        "cycle_start_date": dateConverter(ci.cycle.start_date),
+                        "cycle_end_date": dateConverter(ci.cycle.end_date),
+                    }
+                    for ci in cycles
+                ]
+                # For backward compatibility, keep the first cycle data at the top level
+                issue_data["cycle_name"] = cycles[0].cycle.name
+                issue_data["cycle_start_date"] = dateConverter(cycles[0].cycle.start_date)
+                issue_data["cycle_end_date"] = dateConverter(cycles[0].cycle.end_date)

Then update the generate_table_row and generate_json_row functions to handle multiple cycles.

🧹 Nitpick comments (3)
apiserver/plane/bgtasks/export_task.py (3)

178-184: Improve readability of long strings and fix line length issues.

The comment formatting logic creates lines that exceed the recommended line length of 88 characters.

Consider reformatting to improve readability:

-        ", ".join(
-            [
-                f"{comment['comment']} ({comment['created_at']} by {comment['created_by']})"
-                for comment in issue["comments"]
-            ]
-        )
-        if issue["comments"]
-        else "",
+        ", ".join([
+            f"{comment['comment']} "
+            f"({comment['created_at']} by {comment['created_by']})"
+            for comment in issue["comments"]
+        ]) if issue["comments"] else "",
🧰 Tools
🪛 Ruff (0.8.2)

179-179: Line too long (92 > 88)

(E501)


375-375: Improve approach to user name formatting.

The full name construction appears multiple times in the code and could be extracted into a helper function.

Consider adding a helper function at the top of the file:

def format_user_name(user):
    """Format user's full name or return empty string if user is None."""
    if not user:
        return ""
    return f"{user.first_name} {user.last_name}".strip() or user.email

Then use it throughout the code where user names are formatted.

🧰 Tools
🪛 Ruff (0.8.2)

375-375: Line too long (92 > 88)

(E501)


205-205: JSON key name does not match data structure.

The JSON key is still named "Assignee" but now contains a list of assignees.

Apply this change for consistency:

-        "Assignee": issue["assignees"],
+        "Assignees": issue["assignees"],

This will also need to be updated in the update_json_row function at lines 236-237 and 239.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a695a8b and bebb296.

📒 Files selected for processing (1)
  • apiserver/plane/bgtasks/export_task.py (7 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
apiserver/plane/bgtasks/export_task.py

179-179: Line too long (92 > 88)

(E501)


375-375: Line too long (92 > 88)

(E501)


385-385: Line too long (104 > 88)

(E501)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Analyze (python)
🔇 Additional comments (5)
apiserver/plane/bgtasks/export_task.py (5)

341-348: Efficient query optimization for attachments.

Good job using a single query to fetch all file assets and using defaultdict to organize them by issue ID. This is more efficient than making separate queries for each issue.


315-339: Improved data fetching with related objects.

The use of select_related and prefetch_related is excellent for reducing database queries. You've included all necessary relationships which will significantly improve performance of the export task.


461-467: Efficient per-project grouping optimization.

Great improvement using defaultdict for grouping issues by project ID. This changes the grouping cost from O(n × p) to O(n) with no extra DB queries.


444-451: Update to comprehensive CSV headers.

Great addition of new columns to the exports including comments, estimate, links, and attachment information. This provides a more complete view of issue data to users.


19-20: Good import cleanup and use of defaultdict.

Nice job removing unused imports and adding defaultdict which is used effectively for grouping data.

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

♻️ Duplicate comments (2)
apiserver/plane/bgtasks/export_task.py (2)

409-417: ⚠️ Potential issue

Fix cycle data handling to support multiple cycles.

The current implementation overwrites cycle data in each iteration of the loop, causing only the last cycle to be exported. If an issue can belong to multiple cycles, this implementation will lose data.

-            # Get prefetched cycles and modules
-            cycles = list(issue.issue_cycle.all())
-
-            # Update cycle data
-            for cycle in cycles:
-                issue_data["cycle_name"] = cycle.cycle.name
-                issue_data["cycle_start_date"] = dateConverter(cycle.cycle.start_date)
-                issue_data["cycle_end_date"] = dateConverter(cycle.cycle.end_date)
+            # Get prefetched cycles and modules
+            cycles = list(issue.issue_cycle.all())
+
+            # Store cycles as a list to support multiple cycles per issue
+            if cycles:
+                issue_data["cycles"] = [
+                    {
+                        "cycle_name": cycle.cycle.name,
+                        "cycle_start_date": dateConverter(cycle.cycle.start_date),
+                        "cycle_end_date": dateConverter(cycle.cycle.end_date),
+                    }
+                    for cycle in cycles
+                ]
+                # For backward compatibility, keep the first cycle in the flat fields
+                issue_data["cycle_name"] = cycles[0].cycle.name
+                issue_data["cycle_start_date"] = dateConverter(cycles[0].cycle.start_date)
+                issue_data["cycle_end_date"] = dateConverter(cycles[0].cycle.end_date)
+            else:
+                issue_data["cycles"] = []
+                issue_data["cycle_name"] = ""
+                issue_data["cycle_start_date"] = ""
+                issue_data["cycle_end_date"] = ""

383-383: 💡 Verification agent

🧩 Analysis chain

Add null check for created_by field.

The created_by field is potentially nullable in the Issue model, which could lead to a NoneType attribute error.

-                "created_by": f"{issue.created_by.first_name} {issue.created_by.last_name}",
+                "created_by": f"{issue.created_by.first_name} {issue.created_by.last_name}" if issue.created_by else "",

🏁 Script executed:

#!/bin/bash
# Check if created_by can be null in the Issue model
rg -n "created_by\s*=" -A5 -B5 apiserver/plane/db/models/issue.py
# Check base model definition that Issue likely inherits from
rg -n "class BaseModel" -A20 apiserver/plane/db/models/base.py

Length of output: 900


🏁 Script executed:

#!/bin/bash
# Locate the AuditModel to inspect where created_by is declared
rg -n 'class AuditModel' -R apiserver/plane/db/models

# Find the field definition of created_by across all models
rg -n 'created_by\s*=' -R apiserver/plane/db/models

Length of output: 919


🏁 Script executed:

#!/bin/bash
# Locate the file that defines AuditModel
audit_file=$(rg -l "class AuditModel" apiserver/plane/db/models)
echo "Found AuditModel in: $audit_file"

# Show its definition and any created_by field
sed -n '1,200p' "$audit_file" | sed -n '/class AuditModel/,/class /p'
rg -n "created_by\s*=" "$audit_file"

Length of output: 292


🏁 Script executed:

#!/bin/bash
# Search for where AuditModel is defined
grep -R "class AuditModel" -n apiserver/plane/db/models

# Search all occurrences of created_by field declarations in the models directory
grep -R "created_by\s*=" -n apiserver/plane/db/models

Length of output: 261


🏁 Script executed:

#!/bin/bash
# List all model files to locate where AuditModel might be defined
ls -1 apiserver/plane/db/models

# Show the top of base.py to find the import of AuditModel
sed -n '1,50p' apiserver/plane/db/models/base.py

Length of output: 1519


🏁 Script executed:

#!/bin/bash
# Locate where AuditModel is defined in mixins
grep -R "class AuditModel" -n apiserver/plane/db/mixins

# Inspect the file defining AuditModel for created_by field declaration
grep -R "created_by\s*=" -n apiserver/plane/db/mixins

Length of output: 231


🏁 Script executed:

#!/bin/bash
# List the contents of the db directory to locate mixins
ls -1 apiserver/plane/db

# Search for AuditModel definition in the db directory
grep -R "class AuditModel" -n apiserver/plane/db

# If mixins.py exists, print its content to inspect created_by declaration
if [ -f apiserver/plane/db/mixins.py ]; then
  sed -n '1,200p' apiserver/plane/db/mixins.py
fi

Length of output: 2682


Ensure created_by is null-safe to prevent NoneType errors

The created_by field is declared nullable (null=True) in UserAuditModel and can be unset when there’s no authenticated user. Guard against None before accessing first_name/last_name.

• File: apiserver/plane/bgtasks/export_task.py
Line: ~383

Suggested change:

-                "created_by": f"{issue.created_by.first_name} {issue.created_by.last_name}",
+                "created_by": (
+                    f"{issue.created_by.first_name} {issue.created_by.last_name}"
+                    if issue.created_by else ""
+                ),
🧰 Tools
🪛 Ruff (0.8.2)

383-383: Line too long (92 > 88)

(E501)

🧹 Nitpick comments (2)
apiserver/plane/bgtasks/export_task.py (2)

351-358: Consider using consistent naming for issue-related fields.

There's a potential source of confusion with variable naming. You're annotating issue_id as work_item_id but then referencing it in the context of attachments.

-        file_assets = FileAsset.objects.filter(
-            issue_id__in=workspace_issues.values_list("id", flat=True)
-        ).annotate(work_item_id=F("issue_id"), asset_id=F("id"))
-
-        attachment_dict = defaultdict(list)
-        for asset in file_assets:
-            attachment_dict[asset.work_item_id].append(asset.asset_id)
+        file_assets = FileAsset.objects.filter(
+            issue_id__in=workspace_issues.values_list("id", flat=True)
+        ).annotate(asset_id=F("id"))
+
+        attachment_dict = defaultdict(list)
+        for asset in file_assets:
+            attachment_dict[asset.issue_id].append(asset.asset_id)

389-389: Consider breaking long lines for better readability.

Some lines exceed the standard line length of 88 characters, making the code harder to read.

-                        "created_by": f"{comment.created_by.first_name} {comment.created_by.last_name}",
+                        "created_by": (
+                            f"{comment.created_by.first_name} {comment.created_by.last_name}"
+                            if comment.created_by else ""
+                        ),
🧰 Tools
🪛 Ruff (0.8.2)

389-389: Line too long (104 > 88)

(E501)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bebb296 and 563561c.

📒 Files selected for processing (1)
  • apiserver/plane/bgtasks/export_task.py (7 hunks)
🧰 Additional context used
🪛 Ruff (0.8.2)
apiserver/plane/bgtasks/export_task.py

179-179: Line too long (92 > 88)

(E501)


383-383: Line too long (92 > 88)

(E501)


389-389: Line too long (104 > 88)

(E501)

⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Analyze (python)
🔇 Additional comments (5)
apiserver/plane/bgtasks/export_task.py (5)

13-24: Good job cleaning up imports and adding necessary models.

The addition of the Django ORM tools like Prefetch and use of defaultdict for grouping shows a thoughtful approach to optimization. Also including the necessary models (FileAsset, Label, User) directly improves code clarity.


314-349: Excellent optimization using select_related and prefetch_related.

This is a significant performance improvement. Using select_related and prefetch_related reduces the number of database queries by fetching related objects in a single query or in a more efficient batch, which is crucial for exporting potentially large amounts of data.


457-463: Excellent optimization using defaultdict for project grouping.

Using defaultdict to group issues by project ID in a single pass is more efficient than the previous approach. This ensures that issues are only iterated once rather than having to check each issue for each project ID.


440-447: Added comprehensive headers for enhanced export data.

The addition of new columns in the CSV/Excel export headers like "Comments", "Estimate", "Link", "Assignees", "Subscribers Count", etc. improves the data exports by including more relevant information.


177-191:

Details

✅ Verification successful

Long text content in CSV cells may have format issues.

When exporting comments with complex text that includes formatting characters like commas, quotes, or newlines, you might encounter CSV formatting issues despite using csv.QUOTE_ALL.

Consider testing with complex comment data to ensure proper escaping in CSV exports. You might need to pre-process comment text to replace problematic characters.


🏁 Script executed:

#!/bin/bash
# Check if there's any comment sanitization in the codebase
rg -n "def.*stripped" apiserver/plane/db/models/

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Locate where the CSV writer is instantiated and check quoting settings
rg -n "csv\.writer" -n apiserver/plane/bgtasks/export_task.py
rg -n "quoting" -n apiserver/plane/bgtasks/export_task.py

Length of output: 283


CSV quoting is already correctly configured

The export task instantiates the writer with quoting=csv.QUOTE_ALL, so the built-in Python CSV writer will automatically wrap every field in quotes and escape internal commas, double-quotes (as ""), and newlines per the CSV specification. No additional preprocessing of comment text is needed.

If you do encounter CSV consumers that don’t properly support quoted newlines, consider normalizing line breaks (e.g. replacing \n/\r with spaces) at the point of consumption rather than in the export itself.

🧰 Tools
🪛 Ruff (0.8.2)

179-179: Line too long (92 > 88)

(E501)

@sriramveeraghanta sriramveeraghanta merged commit 13c46e0 into preview May 9, 2025
5 of 6 checks passed
@sriramveeraghanta sriramveeraghanta deleted the chore-project-export-func-enhancement branch May 9, 2025 15:39
al-jaber-nishad pushed a commit to Xinotrix-Home/task-and-meeting-management that referenced this pull request May 27, 2025
…7002)

* chore: comment details of work item

* chore: attachment count and attachment name

* chore: issue link and subscriber count

* chore: list of assignees

* chore: asset_url as attachment_links

* chore: code refactor

* fix: cannot export Excel

* chore: remove print statements

* fix: filtering in list

* chore: optimize attachment_count and attachment_link query

* chore: optimize fetching issue details for multiple select

* chore: use Prefetch to avoid duplicates
lifeiscontent pushed a commit that referenced this pull request Aug 18, 2025
* chore: comment details of work item

* chore: attachment count and attachment name

* chore: issue link and subscriber count

* chore: list of assignees

* chore: asset_url as attachment_links

* chore: code refactor

* fix: cannot export Excel

* chore: remove print statements

* fix: filtering in list

* chore: optimize attachment_count and attachment_link query

* chore: optimize fetching issue details for multiple select

* chore: use Prefetch to avoid duplicates
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[bug]: Exported file shows irrelevant "none" text in Created By column

4 participants