diff --git a/apiserver/plane/app/urls/workspace.py b/apiserver/plane/app/urls/workspace.py index 739d17c55..c449c0fcc 100644 --- a/apiserver/plane/app/urls/workspace.py +++ b/apiserver/plane/app/urls/workspace.py @@ -18,6 +18,7 @@ from plane.app.views import ( WorkspaceUserProfileEndpoint, WorkspaceUserProfileIssuesEndpoint, WorkspaceLabelsEndpoint, + WorkspaceUserProfileIssuesGroupedEndpoint ) @@ -189,6 +190,11 @@ urlpatterns = [ WorkspaceUserProfileIssuesEndpoint.as_view(), name="workspace-user-profile-issues", ), + path( + "v3/workspaces//user-issues//", + WorkspaceUserProfileIssuesGroupedEndpoint.as_view(), + name="workspace-user-profile-issues", + ), path( "workspaces//labels/", WorkspaceLabelsEndpoint.as_view(), diff --git a/apiserver/plane/app/views/__init__.py b/apiserver/plane/app/views/__init__.py index e36d6a14b..ab81f99c9 100644 --- a/apiserver/plane/app/views/__init__.py +++ b/apiserver/plane/app/views/__init__.py @@ -44,6 +44,7 @@ from .workspace import ( WorkspaceUserProfileEndpoint, WorkspaceUserProfileIssuesEndpoint, WorkspaceLabelsEndpoint, + WorkspaceUserProfileIssuesGroupedEndpoint ) from .state import StateViewSet from .view import ( diff --git a/apiserver/plane/app/views/workspace.py b/apiserver/plane/app/views/workspace.py index 637fc95b5..b324ec152 100644 --- a/apiserver/plane/app/views/workspace.py +++ b/apiserver/plane/app/views/workspace.py @@ -1313,6 +1313,62 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView): return Response(issues, status=status.HTTP_200_OK) + +class WorkspaceUserProfileIssuesGroupedEndpoint(BaseAPIView): + + permission_classes = [ + WorkspaceViewerPermission, + ] + + def get(self, request, slug, user_id): + filters = issue_filters(request.query_params, "GET") + fields = [field for field in request.GET.get("fields", "").split(",") if field] + + issue_queryset = ( + Issue.issue_objects.filter( + Q(assignees__in=[user_id]) + | Q(created_by_id=user_id) + | Q(issue_subscribers__subscriber_id=user_id), + workspace__slug=slug, + project__project_projectmember__member=request.user, + ) + .filter(**filters) + .annotate( + sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + .select_related("project", "workspace", "state", "parent") + .prefetch_related("assignees", "labels") + .prefetch_related( + Prefetch( + "issue_reactions", + queryset=IssueReaction.objects.select_related("actor"), + ) + ) + .order_by("-created_at") + .annotate( + link_count=IssueLink.objects.filter(issue=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + .annotate( + attachment_count=IssueAttachment.objects.filter(issue=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + ).distinct() + + issues = IssueLiteSerializer(issue_queryset, many=True, fields=fields if fields else None).data + issue_dict = {str(issue["id"]): issue for issue in issues} + return Response( + issue_dict, + status=status.HTTP_200_OK, + ) + class WorkspaceLabelsEndpoint(BaseAPIView): permission_classes = [ WorkspaceViewerPermission, diff --git a/apiserver/plane/space/views/base.py b/apiserver/plane/space/views/base.py index 49b00f90f..b1d749a09 100644 --- a/apiserver/plane/space/views/base.py +++ b/apiserver/plane/space/views/base.py @@ -78,7 +78,7 @@ class BaseViewSet(TimezoneMixin, ModelViewSet, BasePaginator): if isinstance(e, ValidationError): return Response( - {"error": "Please provide valid detail", "e":str(e)}, + {"error": "Please provide valid detail"}, status=status.HTTP_400_BAD_REQUEST, ) @@ -167,7 +167,7 @@ class BaseAPIView(TimezoneMixin, APIView, BasePaginator): if isinstance(e, ValidationError): return Response( - {"error": "Please provide valid detail" ,"e":str(e)}, + {"error": "Please provide valid detail"}, status=status.HTTP_400_BAD_REQUEST, )