diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index 5888b759c..113b54d0e 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -49,6 +49,7 @@ class IssueFlatSerializer(BaseSerializer): "target_date", "sequence_id", "sort_order", + "is_draft", ] diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index 1d4a16eb6..2b83b0b94 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -1038,6 +1038,7 @@ urlpatterns = [ IssueDraftViewSet.as_view( { "get": "list", + "post": "create", } ), name="project-issue-draft", @@ -1047,6 +1048,7 @@ urlpatterns = [ IssueDraftViewSet.as_view( { "get": "retrieve", + "patch": "partial_update", "delete": "destroy", } ), diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index b6dcb88d5..16dce6f47 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -508,7 +508,7 @@ class IssueActivityEndpoint(BaseAPIView): issue_activities = ( IssueActivity.objects.filter(issue_id=issue_id) .filter( - ~Q(field__in=["comment", "vote", "reaction"]), + ~Q(field__in=["comment", "vote", "reaction", "draft"]), project__project_projectmember__member=self.request.user, ) .select_related("actor", "workspace", "issue", "project") @@ -2358,6 +2358,47 @@ class IssueDraftViewSet(BaseViewSet): serializer_class = IssueFlatSerializer model = Issue + + def perform_update(self, serializer): + requested_data = json.dumps(self.request.data, cls=DjangoJSONEncoder) + current_instance = ( + self.get_queryset().filter(pk=self.kwargs.get("pk", None)).first() + ) + if current_instance is not None: + issue_activity.delay( + type="issue_draft.activity.updated", + requested_data=requested_data, + 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( + IssueSerializer(current_instance).data, cls=DjangoJSONEncoder + ), + ) + + return super().perform_update(serializer) + + + def perform_destroy(self, instance): + current_instance = ( + self.get_queryset().filter(pk=self.kwargs.get("pk", None)).first() + ) + if current_instance is not None: + issue_activity.delay( + type="issue_draft.activity.deleted", + requested_data=json.dumps( + {"issue_id": str(self.kwargs.get("pk", None))} + ), + 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( + IssueSerializer(current_instance).data, cls=DjangoJSONEncoder + ), + ) + return super().perform_destroy(instance) + + def get_queryset(self): return ( Issue.objects.annotate( @@ -2383,6 +2424,7 @@ class IssueDraftViewSet(BaseViewSet): ) ) + @method_decorator(gzip_page) def list(self, request, slug, project_id): try: @@ -2492,6 +2534,40 @@ class IssueDraftViewSet(BaseViewSet): ) + def create(self, request, slug, project_id): + try: + project = Project.objects.get(pk=project_id) + + serializer = IssueCreateSerializer( + data=request.data, + context={ + "project_id": project_id, + "workspace_id": project.workspace_id, + "default_assignee_id": project.default_assignee_id, + }, + ) + + if serializer.is_valid(): + serializer.save(is_draft=True) + + # Track the issue + issue_activity.delay( + type="issue_draft.activity.created", + requested_data=json.dumps(self.request.data, cls=DjangoJSONEncoder), + actor_id=str(request.user.id), + issue_id=str(serializer.data.get("id", None)), + project_id=str(project_id), + current_instance=None, + ) + return Response(serializer.data, status=status.HTTP_201_CREATED) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) + + except Project.DoesNotExist: + return Response( + {"error": "Project was not found"}, status=status.HTTP_404_NOT_FOUND + ) + + def retrieve(self, request, slug, project_id, pk=None): try: issue = Issue.objects.get( diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py index 2d13afc35..73fd54a7e 100644 --- a/apiserver/plane/bgtasks/issue_activites_task.py +++ b/apiserver/plane/bgtasks/issue_activites_task.py @@ -396,16 +396,16 @@ def track_assignees( def create_issue_activity( requested_data, current_instance, issue_id, project, actor, issue_activities ): - issue_activities.append( - IssueActivity( - issue_id=issue_id, - project=project, - workspace=project.workspace, - comment=f"created the issue", - verb="created", - actor=actor, + issue_activities.append( + IssueActivity( + issue_id=issue_id, + project=project, + workspace=project.workspace, + comment=f"created the issue", + verb="created", + actor=actor, + ) ) - ) def track_estimate_points( @@ -518,11 +518,6 @@ def update_issue_activity( "closed_to": track_closed_to, } - requested_data = json.loads(requested_data) if requested_data is not None else None - current_instance = ( - json.loads(current_instance) if current_instance is not None else None - ) - for key in requested_data: func = ISSUE_ACTIVITY_MAPPER.get(key, None) if func is not None: @@ -1095,6 +1090,69 @@ def delete_issue_relation_activity( ) +def create_draft_issue_activity( + requested_data, current_instance, issue_id, project, actor, issue_activities +): + issue_activities.append( + IssueActivity( + issue_id=issue_id, + project=project, + workspace=project.workspace, + comment=f"drafted the issue", + field="draft", + verb="created", + actor=actor, + ) + ) + + +def update_draft_issue_activity( + requested_data, current_instance, issue_id, project, actor, issue_activities +): + requested_data = json.loads(requested_data) if requested_data is not None else None + current_instance = ( + json.loads(current_instance) if current_instance is not None else None + ) + if requested_data.get("is_draft") is not None and requested_data.get("is_draft") == False: + issue_activities.append( + IssueActivity( + issue_id=issue_id, + project=project, + workspace=project.workspace, + comment=f"created the issue", + verb="updated", + actor=actor, + ) + ) + else: + issue_activities.append( + IssueActivity( + issue_id=issue_id, + project=project, + workspace=project.workspace, + comment=f"updated the draft issue", + field="draft", + verb="updated", + actor=actor, + ) + ) + + + +def delete_draft_issue_activity( + requested_data, current_instance, issue_id, project, actor, issue_activities +): + issue_activities.append( + IssueActivity( + project=project, + workspace=project.workspace, + comment=f"deleted the draft issue", + field="draft", + verb="deleted", + actor=actor, + ) + ) + # Receive message from room group @shared_task def issue_activity( @@ -1166,6 +1224,9 @@ def issue_activity( "comment_reaction.activity.deleted": delete_comment_reaction_activity, "issue_vote.activity.created": create_issue_vote_activity, "issue_vote.activity.deleted": delete_issue_vote_activity, + "issue_draft.activity.created": create_draft_issue_activity, + "issue_draft.activity.updated": update_draft_issue_activity, + "issue_draft.activity.deleted": delete_draft_issue_activity, } func = ACTIVITY_MAPPER.get(type)