Skip to content

Comments

[WEB-2718] fix: background task metadata#5909

Merged
pushya22 merged 1 commit intopreviewfrom
chore/file-asset-background-task
Oct 24, 2024
Merged

[WEB-2718] fix: background task metadata#5909
pushya22 merged 1 commit intopreviewfrom
chore/file-asset-background-task

Conversation

@NarayanBavisetti
Copy link
Collaborator

@NarayanBavisetti NarayanBavisetti commented Oct 24, 2024

fix:

  • this pull request fixes the issue where the creator field was updating to null when updating the metadata for file uploads.

Issue Link: WEB-2718

Summary by CodeRabbit

  • New Features

    • Enhanced save operations across various asset management endpoints for improved efficiency by updating only specific fields in the database.
  • Bug Fixes

    • Optimized error handling and response structures remain intact, ensuring consistent API behavior.
  • Chores

    • General improvements in the codebase to streamline asset management processes without altering existing functionalities.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 24, 2024

Walkthrough

The changes in this pull request primarily focus on optimizing the save() method calls across various asset-related classes in the application. The modifications specify the fields to be updated using the update_fields parameter, allowing for more efficient database operations by limiting updates to only the necessary fields. This update affects classes such as FileAssetEndpoint, UserAssetsV2Endpoint, and others, while maintaining the overall logic and control flow of the methods.

Changes

File Path Change Summary
apiserver/plane/app/views/asset/base.py Updated save() calls in FileAssetEndpoint, FileAssetViewSet, and UserAssetsEndpoint to specify update_fields=["is_deleted"].
apiserver/plane/app/views/asset/v2.py Changed save() calls in UserAssetsV2Endpoint, WorkspaceFileAssetEndpoint, AssetRestoreEndpoint, and ProjectAssetEndpoint to specify fields like is_deleted, deleted_at, is_uploaded, and attributes. Updated method signatures without parameter changes.
apiserver/plane/bgtasks/storage_metadata_task.py Modified get_asset_object_metadata to use asset.save(update_fields=["storage_metadata"]) for optimized saving.
apiserver/plane/space/views/asset.py Updated save() calls in EntityAssetEndpoint and AssetRestoreEndpoint to specify fields like attributes, is_uploaded, is_deleted, and deleted_at.

Possibly related PRs

  • fix: private bucket  #5812: This PR modifies the UserAssetsV2Endpoint, WorkspaceFileAssetEndpoint, and ProjectAssetEndpoint classes, focusing on the save() method calls, similar to the changes in the main PR where save() is updated to specify update_fields.
  • dev: move storage metadata collection to background job #5818: This PR also updates the patch methods in UserAssetsV2Endpoint and ProjectAssetEndpoint, replacing direct calls to retrieve storage metadata with an asynchronous task, which aligns with the focus on asset management in the main PR.
  • [WEB-2718] fix: issue attachment uploads #5904: This PR updates the post, patch, and delete methods across several asset-related classes, ensuring that the created_by attribute is set correctly, which is relevant to the changes in the main PR regarding the save() method.

Suggested labels

🐛bug, ⚙️backend

Suggested reviewers

  • pablohashescobar
  • sriramveeraghanta
  • SatishGandham

🐇 In the code where assets dwell,
We save with care, oh so well.
Just fields we touch, no more, no less,
Optimized and swift, we do our best!
With each small change, our code will gleam,
A rabbit's hop towards the dream! 🐇


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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.

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 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.

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

🧹 Outside diff range and nitpick comments (5)
apiserver/plane/app/views/asset/base.py (1)

Line range hint 1-99: Consider creating a base mixin for asset operations.

There's significant duplication in the soft delete/restore logic across endpoints. Consider creating a mixin class to encapsulate common functionality:

  1. Error handling
  2. Permission checks
  3. Soft delete/restore operations

Example implementation:

class AssetOperationMixin:
    def perform_asset_operation(self, asset_key, operation, workspace_id=None):
        try:
            filters = {"asset": asset_key}
            if workspace_id:
                if not self.request.user.has_workspace_permission(workspace_id, operation):
                    return Response(
                        {"error": f"You don't have permission to {operation} this asset"},
                        status=status.HTTP_403_FORBIDDEN
                    )
            else:
                filters["created_by"] = self.request.user

            file_asset = FileAsset.objects.get(**filters)
            
            if operation == "delete":
                file_asset.is_deleted = True
            elif operation == "restore":
                file_asset.is_deleted = False
                
            file_asset.save(update_fields=["is_deleted"])
            return Response(status=status.HTTP_204_NO_CONTENT)
            
        except FileAsset.DoesNotExist:
            return Response(
                {"error": "Asset not found"},
                status=status.HTTP_404_NOT_FOUND
            )

This would simplify the endpoint implementations and ensure consistent behavior.

apiserver/plane/space/views/asset.py (3)

196-196: LGTM! Consider wrapping in a transaction.

The selective field update for soft deletion is correct and efficient. However, since we're updating multiple fields that should be atomically consistent, consider wrapping the operation in a transaction.

+ from django.db import transaction
...
    def delete(self, request, anchor, pk):
+       with transaction.atomic():
            # ... existing code ...
            asset.is_deleted = True
            asset.deleted_at = timezone.now()
            asset.save(update_fields=["is_deleted", "deleted_at"])

221-221: LGTM! Apply same transaction pattern for consistency.

The selective field update for asset restoration is correct. For consistency with the delete operation, consider applying the same transaction pattern here.

+ from django.db import transaction
...
    def post(self, request, anchor, asset_id):
+       with transaction.atomic():
            # ... existing code ...
            asset.is_deleted = False
            asset.deleted_at = None
            asset.save(update_fields=["is_deleted", "deleted_at"])

Line range hint 172-221: Consider adding logging for asset state changes.

For better observability and debugging, consider adding logging when asset states change (upload completion, deletion, restoration). This would help track the lifecycle of assets and investigate any issues.

Example logging implementation:

import logging

logger = logging.getLogger(__name__)

# In patch method:
logger.info(f"Asset {asset.id} marked as uploaded in workspace {deploy_board.workspace_id}")

# In delete method:
logger.info(f"Asset {asset.id} marked as deleted in workspace {deploy_board.workspace_id}")

# In restore method:
logger.info(f"Asset {asset.id} restored in workspace {deploy_board.workspace_id}")
apiserver/plane/app/views/asset/v2.py (1)

Line range hint 1-800: Security Advisory: Consider additional file upload validations.

While the code includes basic file type and size validations, consider implementing additional security measures:

  1. Content-Type verification using magic numbers
  2. Virus/malware scanning
  3. File name sanitization
  4. Maximum filename length restrictions

Consider implementing a middleware or utility class that handles these security checks consistently across all file upload endpoints.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between 14b31e3 and 650e8ef.

📒 Files selected for processing (4)
  • apiserver/plane/app/views/asset/base.py (3 hunks)
  • apiserver/plane/app/views/asset/v2.py (9 hunks)
  • apiserver/plane/bgtasks/storage_metadata_task.py (1 hunks)
  • apiserver/plane/space/views/asset.py (3 hunks)
🔇 Additional comments (11)
apiserver/plane/bgtasks/storage_metadata_task.py (2)

22-22: LGTM! Good optimization using update_fields.

The change to specify update_fields=["storage_metadata"] is a good optimization that prevents unnecessary field updates and resolves the issue of the creator field being nullified.


22-22: Verify fix for creator field issue.

Let's verify that this change completely resolves the creator field issue by checking the model's behavior.

✅ Verification successful

Let me try a different approach to gather the necessary information.

The previous commands didn't yield results, so I'm trying with broader patterns to understand the model structure and its usage. This will help us verify if the creator field issue is properly addressed in the codebase.


Let me check the migrations to understand the creator field structure and any recent changes.

This will help us understand the model structure, including any creator-related fields from both the base model and migrations.


Let me check one more thing to understand the AuditModel that BaseModel inherits from, as it might contain the creator field.

This will help us understand the complete model structure and the storage_metadata field's context.


Let me check one final thing to see the UserAuditModel that contains the creator field.

This will give us the complete picture of the model inheritance chain and creator field implementation.


Now I have enough information to provide a final response about the creator field issue.

The creator field is properly handled - no issues found

The FileAsset model inherits from BaseModel which in turn inherits from AuditModel (combining TimeAuditModel, UserAuditModel, and SoftDeleteModel). The creator field is properly implemented through UserAuditModel as created_by with a foreign key to the User model. The code in question only updates the storage_metadata field using update_fields, which is a safe operation that won't affect the creator field.

Key findings:

  • The creator field (created_by) is properly defined in UserAuditModel
  • The storage_metadata update is isolated and doesn't impact other fields
  • The migration (0079) preserves the creator information during data migration
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify FileAsset model fields and their handling

# Check model definition for creator field
ast-grep --pattern 'class FileAsset(models.Model):
  $$$
  creator = $$$
  $$$'

# Check for any other places where FileAsset is saved
rg -l "FileAsset.*save\(" --type py

# Check for any migrations that might affect the creator field
fd -e py -p ".*migrations.*" | xargs rg "creator"

Length of output: 169


Script:

#!/bin/bash
# Let's first locate the FileAsset model definition
rg -A 20 "class FileAsset" --type py

# Check all references to FileAsset and save operations
rg -B 2 -A 2 "FileAsset.*save" --type py

# Check for any creator field usage with FileAsset
rg -B 2 -A 2 "FileAsset.*creator" --type py

# Look for migrations containing FileAsset
rg -l "FileAsset" --type py | grep "migrations"

Length of output: 5495


Script:

#!/bin/bash
# Check the recent migration that might be related
cat apiserver/plane/db/migrations/0079_auto_20241009_0619.py

# Check the initial migration for FileAsset model structure
cat apiserver/plane/db/migrations/0001_initial.py | grep -A 20 "class FileAsset"

# Let's also check the BaseModel since FileAsset inherits from it
rg -A 10 "class BaseModel" --type py

Length of output: 2878


Script:

#!/bin/bash
# Check AuditModel definition
rg -A 10 "class AuditModel" --type py

# Also check the storage_metadata field usage since that's what's being updated
rg -B 2 -A 2 "storage_metadata" --type py

Length of output: 5293


Script:

#!/bin/bash
# Check UserAuditModel definition which should contain creator field
rg -A 10 "class UserAuditModel" --type py

# Also check TimeAuditModel and SoftDeleteModel for completeness
rg -A 10 "class (TimeAuditModel|SoftDeleteModel)" --type py

Length of output: 1968

apiserver/plane/app/views/asset/base.py (1)

53-53: Changes successfully address the creator field issue.

The addition of update_fields=["is_deleted"] to all save() calls effectively prevents the creator field from being accidentally updated to null during metadata updates. This is a good fix that:

  1. Solves the specific issue
  2. Improves performance by limiting field updates
  3. Prevents similar issues with other fields

Also applies to: 62-62, 99-99

apiserver/plane/space/views/asset.py (1)

172-172: LGTM! Optimized save operation prevents unintended field updates.

The selective field update ensures only the modified fields (attributes and is_uploaded) are saved, preventing any unintended changes to other fields like creator. This change not only fixes the reported issue but also optimizes the database operation.

apiserver/plane/app/views/asset/v2.py (7)

37-37: LGTM! Optimized save operation.

The update_fields parameter correctly specifies only the modified fields, preventing unintended updates to other fields.


223-223: LGTM! Consistent with asset_delete implementation.

The update_fields parameter matches the asset_delete method implementation, maintaining consistency across the codebase.


282-282: LGTM! Maintains consistency across endpoint classes.

The update_fields parameter is consistent with the UserAssetsV2Endpoint implementation, following the same pattern for asset deletion.


462-462: LGTM! Consistent implementation across endpoint methods.

The update_fields parameters in both patch and delete methods are consistent with their counterparts in UserAssetsV2Endpoint.

Also applies to: 473-473


552-552: LGTM! Consistent with deletion operations.

The update_fields parameter matches other deletion-related operations, maintaining consistency in how these fields are handled.


693-693: LGTM! Consistent implementation across all endpoint classes.

The update_fields parameters in both patch and delete methods maintain consistency with other endpoint implementations.

Also applies to: 708-708


212-212: Consider potential race condition in metadata handling.

While the save optimization is correct, there's a potential race condition between checking if not asset.storage_metadata and saving the asset. The background task get_asset_object_metadata might update the metadata while we're saving here.

Consider implementing a synchronization mechanism or handling concurrent metadata updates gracefully.

)
file_asset.is_deleted = True
file_asset.save()
file_asset.save(update_fields=["is_deleted"])
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

Add error handling for the delete operation.

While this endpoint correctly scopes to user's own assets, it still needs error handling.

Consider implementing these changes:

 def delete(self, request, asset_key):
-    file_asset = FileAsset.objects.get(
-        asset=asset_key, created_by=request.user
-    )
-    file_asset.is_deleted = True
-    file_asset.save(update_fields=["is_deleted"])
-    return Response(status=status.HTTP_204_NO_CONTENT)
+    try:
+        file_asset = FileAsset.objects.get(
+            asset=asset_key, created_by=request.user
+        )
+        file_asset.is_deleted = True
+        file_asset.save(update_fields=["is_deleted"])
+        return Response(status=status.HTTP_204_NO_CONTENT)
+    except FileAsset.DoesNotExist:
+        return Response(
+            {"error": "Asset not found"},
+            status=status.HTTP_404_NOT_FOUND
+        )

Committable suggestion was skipped due to low confidence.

file_asset = FileAsset.objects.get(asset=asset_key)
file_asset.is_deleted = True
file_asset.save()
file_asset.save(update_fields=["is_deleted"])
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

Add permission checks and error handling.

While the update_fields optimization is good, this endpoint needs additional safeguards:

  1. Add permission checks to ensure the user has delete rights
  2. Add error handling for non-existent assets

Consider implementing these changes:

 def delete(self, request, workspace_id, asset_key):
     asset_key = str(workspace_id) + "/" + asset_key
-    file_asset = FileAsset.objects.get(asset=asset_key)
-    file_asset.is_deleted = True
-    file_asset.save(update_fields=["is_deleted"])
-    return Response(status=status.HTTP_204_NO_CONTENT)
+    try:
+        file_asset = FileAsset.objects.get(asset=asset_key)
+        
+        # Add permission check
+        if not request.user.has_workspace_permission(workspace_id, "delete"):
+            return Response(
+                {"error": "You don't have permission to delete this asset"},
+                status=status.HTTP_403_FORBIDDEN
+            )
+            
+        file_asset.is_deleted = True
+        file_asset.save(update_fields=["is_deleted"])
+        return Response(status=status.HTTP_204_NO_CONTENT)
+    except FileAsset.DoesNotExist:
+        return Response(
+            {"error": "Asset not found"},
+            status=status.HTTP_404_NOT_FOUND
+        )

Committable suggestion was skipped due to low confidence.

file_asset = FileAsset.objects.get(asset=asset_key)
file_asset.is_deleted = False
file_asset.save()
file_asset.save(update_fields=["is_deleted"])
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

Add permission checks and error handling for restore operation.

Similar to the delete endpoint, this restore operation needs additional safeguards.

Consider implementing these changes:

 def restore(self, request, workspace_id, asset_key):
     asset_key = str(workspace_id) + "/" + asset_key
-    file_asset = FileAsset.objects.get(asset=asset_key)
-    file_asset.is_deleted = False
-    file_asset.save(update_fields=["is_deleted"])
-    return Response(status=status.HTTP_204_NO_CONTENT)
+    try:
+        file_asset = FileAsset.objects.get(asset=asset_key)
+        
+        # Add permission check
+        if not request.user.has_workspace_permission(workspace_id, "update"):
+            return Response(
+                {"error": "You don't have permission to restore this asset"},
+                status=status.HTTP_403_FORBIDDEN
+            )
+            
+        file_asset.is_deleted = False
+        file_asset.save(update_fields=["is_deleted"])
+        return Response(status=status.HTTP_204_NO_CONTENT)
+    except FileAsset.DoesNotExist:
+        return Response(
+            {"error": "Asset not found"},
+            status=status.HTTP_404_NOT_FOUND
+        )
📝 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
file_asset.save(update_fields=["is_deleted"])
def restore(self, request, workspace_id, asset_key):
asset_key = str(workspace_id) + "/" + asset_key
try:
file_asset = FileAsset.objects.get(asset=asset_key)
# Add permission check
if not request.user.has_workspace_permission(workspace_id, "update"):
return Response(
{"error": "You don't have permission to restore this asset"},
status=status.HTTP_403_FORBIDDEN
)
file_asset.is_deleted = False
file_asset.save(update_fields=["is_deleted"])
return Response(status=status.HTTP_204_NO_CONTENT)
except FileAsset.DoesNotExist:
return Response(
{"error": "Asset not found"},
status=status.HTTP_404_NOT_FOUND
)

@pushya22 pushya22 merged commit 2caf23f into preview Oct 24, 2024
@pushya22 pushya22 deleted the chore/file-asset-background-task branch October 24, 2024 15:05
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.

3 participants