From a4f095fb5934f347799b18905628557f19289ff4 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Fri, 3 Feb 2023 19:03:27 +0530 Subject: [PATCH 1/4] refactor: update cycle and module issue create --- apiserver/plane/api/views/cycle.py | 56 ++++++++++++++++++----------- apiserver/plane/api/views/module.py | 51 ++++++++++++++++---------- 2 files changed, 69 insertions(+), 38 deletions(-) diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index d1b291d9a..c8c3b7720 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -14,7 +14,6 @@ from plane.db.models import Cycle, CycleIssue, Issue class CycleViewSet(BaseViewSet): - serializer_class = CycleSerializer model = Cycle permission_classes = [ @@ -41,7 +40,6 @@ class CycleViewSet(BaseViewSet): class CycleIssueViewSet(BaseViewSet): - serializer_class = CycleIssueSerializer model = CycleIssue @@ -79,7 +77,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 +88,48 @@ 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)) - # Delete old records in order to maintain the database integrity - CycleIssue.objects.filter(issue_id__in=issues).delete() + records_to_update = [] + record_to_create = [] + + for issue in issues: + cycle_issue = [ + cycle_issue + for cycle_issue in cycle_issues + if cycle_issue.issue_id in issues + ] + if len(cycle_issue): + 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, + ) + + # 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..31d7c7b1c 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -25,7 +25,6 @@ from plane.db.models import ( class ModuleViewSet(BaseViewSet): - model = Module permission_classes = [ ProjectEntityPermission, @@ -95,7 +94,6 @@ class ModuleViewSet(BaseViewSet): class ModuleIssueViewSet(BaseViewSet): - serializer_class = ModuleIssueSerializer model = ModuleIssue @@ -148,28 +146,45 @@ 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() + 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): + 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, ) + + ModuleIssue.objects.bulk_update( + records_to_update, + ["module"], + batch_size=10, + ) + return Response({"message": "Success"}, status=status.HTTP_200_OK) except Module.DoesNotExist: return Response( From 7207d92d6268f1920775db7b2745e1b8e9f37f97 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Fri, 3 Feb 2023 19:03:58 +0530 Subject: [PATCH 2/4] refactor: return modules created --- apiserver/plane/api/views/module.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index 31d7c7b1c..e8546c05b 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -185,7 +185,10 @@ class ModuleIssueViewSet(BaseViewSet): batch_size=10, ) - return Response({"message": "Success"}, status=status.HTTP_200_OK) + 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 From 67a1052b7be4b8103ede7cdb38e95a82e0ad09a9 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 6 Feb 2023 15:31:05 +0530 Subject: [PATCH 3/4] refactor: track cycles and modules for issue --- apiserver/plane/api/views/cycle.py | 44 +++++- apiserver/plane/api/views/module.py | 37 ++++- .../plane/bgtasks/issue_activites_task.py | 137 ++++++++++++++++-- 3 files changed, 197 insertions(+), 21 deletions(-) diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index c8c3b7720..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,6 +15,7 @@ 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): @@ -90,19 +95,28 @@ class CycleIssueViewSet(BaseViewSet): # 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 = [] for issue in issues: cycle_issue = [ cycle_issue for cycle_issue in cycle_issues - if cycle_issue.issue_id in issues + if str(cycle_issue.issue_id) in issues ] + # Update only when cycle changes if len(cycle_issue): - cycle_issue[0].cycle_id = cycle_id - records_to_update.append(cycle_issue[0]) + 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( @@ -126,9 +140,29 @@ class CycleIssueViewSet(BaseViewSet): 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 + CycleIssueSerializer(self.get_queryset(), many=True).data, + status=status.HTTP_200_OK, ) except Cycle.DoesNotExist: diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index e8546c05b..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,6 +26,7 @@ from plane.db.models import ( Issue, ModuleLink, ) +from plane.bgtasks.issue_activites_task import issue_activity class ModuleViewSet(BaseViewSet): @@ -148,6 +153,7 @@ class ModuleIssueViewSet(BaseViewSet): module_issues = list(ModuleIssue.objects.filter(issue_id__in=issues)) + update_module_issue_activity = [] records_to_update = [] record_to_create = [] @@ -159,8 +165,16 @@ class ModuleIssueViewSet(BaseViewSet): ] if len(module_issue): - module_issue[0].module_id = module_id - records_to_update.append(module_issue[0]) + 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( @@ -185,6 +199,25 @@ class ModuleIssueViewSet(BaseViewSet): 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, diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py index f6debc921..2f20f84c8 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,116 @@ 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.cycle_id).first() + issue_activities.append( + IssueActivity( + issue_id=created_record.issue_id, + 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}", + old_identifier="", + 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.module_id).first() + issue_activities.append( + IssueActivity( + issue_id=created_record.issue_id, + 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}", + old_identifier="", + new_identifier=module.id, + ) + ) + + # Receive message from room group @job("default") def issue_activity(event): @@ -510,7 +617,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 +637,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: From a576a2ab5905663b058a302b296f8ee305a49554 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 6 Feb 2023 19:25:11 +0530 Subject: [PATCH 4/4] fix: tracking new cycles and modules in activities --- apiserver/plane/bgtasks/issue_activites_task.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py index 2f20f84c8..7e0e3f6ff 100644 --- a/apiserver/plane/bgtasks/issue_activites_task.py +++ b/apiserver/plane/bgtasks/issue_activites_task.py @@ -536,10 +536,13 @@ def track_cycles( ) for created_record in created_records: - cycle = Cycle.objects.filter(pk=created_record.cycle_id).first() + cycle = Cycle.objects.filter( + pk=created_record.get("fields").get("cycle") + ).first() + issue_activities.append( IssueActivity( - issue_id=created_record.issue_id, + issue_id=created_record.get("fields").get("issue"), actor=actor, verb="created", old_value="", @@ -548,7 +551,6 @@ def track_cycles( project=project, workspace=project.workspace, comment=f"{actor.email} added cycle {cycle.name}", - old_identifier="", new_identifier=cycle.id, ) ) @@ -591,10 +593,12 @@ def track_modules( ) for created_record in created_records: - module = Module.objects.filter(pk=created_record.module_id).first() + module = Module.objects.filter( + pk=created_record.get("fields").get("module") + ).first() issue_activities.append( IssueActivity( - issue_id=created_record.issue_id, + issue_id=created_record.get("fields").get("issue"), actor=actor, verb="created", old_value="", @@ -603,7 +607,6 @@ def track_modules( project=project, workspace=project.workspace, comment=f"{actor.email} added module {module.name}", - old_identifier="", new_identifier=module.id, ) )