diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index d1b291d9a..2b18aab96 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -1,5 +1,9 @@ +# Python imports +import json + # Django imports from django.db.models import OuterRef, Func, F +from django.core import serializers # Third party imports from rest_framework.response import Response @@ -11,10 +15,10 @@ from . import BaseViewSet from plane.api.serializers import CycleSerializer, CycleIssueSerializer from plane.api.permissions import ProjectEntityPermission from plane.db.models import Cycle, CycleIssue, Issue +from plane.bgtasks.issue_activites_task import issue_activity class CycleViewSet(BaseViewSet): - serializer_class = CycleSerializer model = Cycle permission_classes = [ @@ -41,7 +45,6 @@ class CycleViewSet(BaseViewSet): class CycleIssueViewSet(BaseViewSet): - serializer_class = CycleIssueSerializer model = CycleIssue @@ -79,7 +82,6 @@ class CycleIssueViewSet(BaseViewSet): def create(self, request, slug, project_id, cycle_id): try: - issues = request.data.get("issues", []) if not len(issues): @@ -91,29 +93,77 @@ class CycleIssueViewSet(BaseViewSet): workspace__slug=slug, project_id=project_id, pk=cycle_id ) - issues = Issue.objects.filter( - pk__in=issues, workspace__slug=slug, project_id=project_id - ) + # Get all CycleIssues already created + cycle_issues = list(CycleIssue.objects.filter(issue_id__in=issues)) + records_to_update = [] + update_cycle_issue_activity = [] + record_to_create = [] - # Delete old records in order to maintain the database integrity - CycleIssue.objects.filter(issue_id__in=issues).delete() + for issue in issues: + cycle_issue = [ + cycle_issue + for cycle_issue in cycle_issues + if str(cycle_issue.issue_id) in issues + ] + # Update only when cycle changes + if len(cycle_issue): + if cycle_issue[0].cycle_id != cycle_id: + update_cycle_issue_activity.append( + { + "old_cycle_id": str(cycle_issue[0].cycle_id), + "new_cycle_id": str(cycle_id), + "issue_id": str(cycle_issue[0].issue_id), + } + ) + cycle_issue[0].cycle_id = cycle_id + records_to_update.append(cycle_issue[0]) + else: + record_to_create.append( + CycleIssue( + project_id=project_id, + workspace=cycle.workspace, + created_by=request.user, + updated_by=request.user, + cycle=cycle, + issue_id=issue, + ) + ) CycleIssue.objects.bulk_create( - [ - CycleIssue( - project_id=project_id, - workspace=cycle.workspace, - created_by=request.user, - updated_by=request.user, - cycle=cycle, - issue=issue, - ) - for issue in issues - ], + record_to_create, batch_size=10, ignore_conflicts=True, ) - return Response({"message": "Success"}, status=status.HTTP_200_OK) + CycleIssue.objects.bulk_update( + records_to_update, + ["cycle"], + batch_size=10, + ) + + # Capture Issue Activity + issue_activity.delay( + { + "type": "issue.activity", + "requested_data": json.dumps({"cycles_list": issues}), + "actor_id": str(self.request.user.id), + "issue_id": str(self.kwargs.get("pk", None)), + "project_id": str(self.kwargs.get("project_id", None)), + "current_instance": json.dumps( + { + "updated_cycle_issues": update_cycle_issue_activity, + "created_cycle_issues": serializers.serialize( + "json", record_to_create + ), + } + ), + }, + ) + + # Return all Cycle Issues + return Response( + CycleIssueSerializer(self.get_queryset(), many=True).data, + status=status.HTTP_200_OK, + ) except Cycle.DoesNotExist: return Response( diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index 9955ded76..73014fefd 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -1,6 +1,10 @@ +# Python imports +import json + # Django Imports from django.db import IntegrityError from django.db.models import Prefetch, F, OuterRef, Func +from django.core import serializers # Third party imports from rest_framework.response import Response @@ -22,10 +26,10 @@ from plane.db.models import ( Issue, ModuleLink, ) +from plane.bgtasks.issue_activites_task import issue_activity class ModuleViewSet(BaseViewSet): - model = Module permission_classes = [ ProjectEntityPermission, @@ -95,7 +99,6 @@ class ModuleViewSet(BaseViewSet): class ModuleIssueViewSet(BaseViewSet): - serializer_class = ModuleIssueSerializer model = ModuleIssue @@ -148,29 +151,77 @@ class ModuleIssueViewSet(BaseViewSet): workspace__slug=slug, project_id=project_id, pk=module_id ) - issues = Issue.objects.filter( - pk__in=issues, workspace__slug=slug, project_id=project_id - ) + module_issues = list(ModuleIssue.objects.filter(issue_id__in=issues)) - # Delete old records in order to maintain the database integrity - ModuleIssue.objects.filter(issue_id__in=issues).delete() + update_module_issue_activity = [] + records_to_update = [] + record_to_create = [] + + for issue in issues: + module_issue = [ + module_issue + for module_issue in module_issues + if module_issue.issue_id in issues + ] + + if len(module_issue): + if module_issue[0].cycle_id != module_id: + update_module_issue_activity.append( + { + "old_module_id": str(module_issue[0].cycle_id), + "new_module_id": str(module_id), + "issue_id": str(module_issue[0].issue_id), + } + ) + module_issue[0].module_id = module_id + records_to_update.append(module_issue[0]) + else: + record_to_create.append( + ModuleIssue( + module=module, + issue=issue, + project_id=project_id, + workspace=module.workspace, + created_by=request.user, + updated_by=request.user, + ) + ) ModuleIssue.objects.bulk_create( - [ - ModuleIssue( - module=module, - issue=issue, - project_id=project_id, - workspace=module.workspace, - created_by=request.user, - updated_by=request.user, - ) - for issue in issues - ], + record_to_create, batch_size=10, ignore_conflicts=True, ) - return Response({"message": "Success"}, status=status.HTTP_200_OK) + + ModuleIssue.objects.bulk_update( + records_to_update, + ["module"], + batch_size=10, + ) + + # Capture Issue Activity + issue_activity.delay( + { + "type": "issue.activity", + "requested_data": json.dumps({"cycles_list": issues}), + "actor_id": str(self.request.user.id), + "issue_id": str(self.kwargs.get("pk", None)), + "project_id": str(self.kwargs.get("project_id", None)), + "current_instance": json.dumps( + { + "updated_cycle_issues": update_module_issue_activity, + "created_cycle_issues": serializers.serialize( + "json", record_to_create + ), + } + ), + }, + ) + + return Response( + ModuleIssueSerializer(self.get_queryset(), many=True).data, + status=status.HTTP_200_OK, + ) except Module.DoesNotExist: return Response( {"error": "Module Does not exists"}, status=status.HTTP_400_BAD_REQUEST diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py index f6debc921..7e0e3f6ff 100644 --- a/apiserver/plane/bgtasks/issue_activites_task.py +++ b/apiserver/plane/bgtasks/issue_activites_task.py @@ -6,7 +6,16 @@ from django_rq import job from sentry_sdk import capture_exception # Module imports -from plane.db.models import User, Issue, Project, Label, IssueActivity, State +from plane.db.models import ( + User, + Issue, + Project, + Label, + IssueActivity, + State, + Cycle, + Module, +) # Track Chnages in name @@ -44,7 +53,6 @@ def track_parent( issue_activities, ): if current_instance.get("parent") != requested_data.get("parent"): - if requested_data.get("parent") == None: old_parent = Issue.objects.get(pk=current_instance.get("parent")) issue_activities.append( @@ -134,7 +142,6 @@ def track_state( issue_activities, ): if current_instance.get("state") != requested_data.get("state"): - new_state = State.objects.get(pk=requested_data.get("state", None)) old_state = State.objects.get(pk=current_instance.get("state", None)) @@ -167,7 +174,6 @@ def track_description( if current_instance.get("description_html") != requested_data.get( "description_html" ): - issue_activities.append( IssueActivity( issue_id=issue_id, @@ -274,7 +280,6 @@ def track_labels( ): # Label Addition if len(requested_data.get("labels_list")) > len(current_instance.get("labels")): - for label in requested_data.get("labels_list"): if label not in current_instance.get("labels"): label = Label.objects.get(pk=label) @@ -296,7 +301,6 @@ def track_labels( # Label Removal if len(requested_data.get("labels_list")) < len(current_instance.get("labels")): - for label in current_instance.get("labels"): if label not in requested_data.get("labels_list"): label = Label.objects.get(pk=label) @@ -326,12 +330,10 @@ def track_assignees( actor, issue_activities, ): - # Assignee Addition if len(requested_data.get("assignees_list")) > len( current_instance.get("assignees") ): - for assignee in requested_data.get("assignees_list"): if assignee not in current_instance.get("assignees"): assignee = User.objects.get(pk=assignee) @@ -354,7 +356,6 @@ def track_assignees( if len(requested_data.get("assignees_list")) < len( current_instance.get("assignees") ): - for assignee in current_instance.get("assignees"): if assignee not in requested_data.get("assignees_list"): assignee = User.objects.get(pk=assignee) @@ -386,7 +387,6 @@ def track_blocks( if len(requested_data.get("blocks_list")) > len( current_instance.get("blocked_issues") ): - for block in requested_data.get("blocks_list"): if ( len( @@ -418,7 +418,6 @@ def track_blocks( if len(requested_data.get("blocks_list")) < len( current_instance.get("blocked_issues") ): - for blocked in current_instance.get("blocked_issues"): if blocked.get("block") not in requested_data.get("blocks_list"): issue = Issue.objects.get(pk=blocked.get("block")) @@ -450,7 +449,6 @@ def track_blockings( if len(requested_data.get("blockers_list")) > len( current_instance.get("blocker_issues") ): - for block in requested_data.get("blockers_list"): if ( len( @@ -482,7 +480,6 @@ def track_blockings( if len(requested_data.get("blockers_list")) < len( current_instance.get("blocker_issues") ): - for blocked in current_instance.get("blocker_issues"): if blocked.get("blocked_by") not in requested_data.get("blockers_list"): issue = Issue.objects.get(pk=blocked.get("blocked_by")) @@ -502,6 +499,119 @@ def track_blockings( ) +def track_cycles( + requested_data, + current_instance, + issue_id, + project, + actor, + issue_activities, +): + # Updated Records: + updated_records = current_instance.get("updated_cycle_issues", []) + created_records = json.loads(current_instance.get("created_cycle_issues", [])) + + for updated_record in updated_records: + old_cycle = Cycle.objects.filter( + pk=updated_record.get("old_cycle_id", None) + ).first() + new_cycle = Cycle.objects.filter( + pk=updated_record.get("new_cycle_id", None) + ).first() + + issue_activities.append( + IssueActivity( + issue_id=updated_record.get("issue_id"), + actor=actor, + verb="updated", + old_value=old_cycle.name, + new_value=new_cycle.name, + field="cycles", + project=project, + workspace=project.workspace, + comment=f"{actor.email} updated cycle from {old_cycle.name} to {new_cycle.name}", + old_identifier=old_cycle.id, + new_identifier=new_cycle.id, + ) + ) + + for created_record in created_records: + cycle = Cycle.objects.filter( + pk=created_record.get("fields").get("cycle") + ).first() + + issue_activities.append( + IssueActivity( + issue_id=created_record.get("fields").get("issue"), + actor=actor, + verb="created", + old_value="", + new_value=cycle.name, + field="cycles", + project=project, + workspace=project.workspace, + comment=f"{actor.email} added cycle {cycle.name}", + new_identifier=cycle.id, + ) + ) + + +def track_modules( + requested_data, + current_instance, + issue_id, + project, + actor, + issue_activities, +): + # Updated Records: + updated_records = current_instance.get("updated_module_issues", []) + created_records = json.loads(current_instance.get("created_module_issues", [])) + + for updated_record in updated_records: + old_module = Module.objects.filter( + pk=updated_record.get("old_module_id", None) + ).first() + new_module = Module.objects.filter( + pk=updated_record.get("new_module_id", None) + ).first() + + issue_activities.append( + IssueActivity( + issue_id=updated_record.get("issue_id"), + actor=actor, + verb="updated", + old_value=old_module.name, + new_value=new_module.name, + field="modules", + project=project, + workspace=project.workspace, + comment=f"{actor.email} updated module from {old_module.name} to {new_module.name}", + old_identifier=old_module.id, + new_identifier=new_module.id, + ) + ) + + for created_record in created_records: + module = Module.objects.filter( + pk=created_record.get("fields").get("module") + ).first() + issue_activities.append( + IssueActivity( + issue_id=created_record.get("fields").get("issue"), + actor=actor, + verb="created", + old_value="", + new_value=module.name, + field="modules", + project=project, + workspace=project.workspace, + comment=f"{actor.email} added module {module.name}", + new_identifier=module.id, + ) + ) + + # Receive message from room group @job("default") def issue_activity(event): @@ -510,7 +620,7 @@ def issue_activity(event): requested_data = json.loads(event.get("requested_data")) current_instance = json.loads(event.get("current_instance")) - issue_id = event.get("issue_id") + issue_id = event.get("issue_id", None) actor_id = event.get("actor_id") project_id = event.get("project_id") @@ -530,6 +640,8 @@ def issue_activity(event): "assignees_list": track_assignees, "blocks_list": track_blocks, "blockers_list": track_blockings, + "cycles_list": track_cycles, + "modules_list": track_modules, } for key in requested_data: