diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index ae033969f..4477d9328 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -505,7 +505,7 @@ class IssueStateFlatSerializer(BaseSerializer): # Issue Serializer with state details -class IssueStateSerializer(BaseSerializer): +class IssueStateSerializer(DynamicBaseSerializer): label_details = LabelLiteSerializer(read_only=True, source="labels", many=True) state_detail = StateLiteSerializer(read_only=True, source="state") project_detail = ProjectLiteSerializer(read_only=True, source="project") diff --git a/apiserver/plane/api/urls/cycle.py b/apiserver/plane/api/urls/cycle.py index 068276361..7e6f014fc 100644 --- a/apiserver/plane/api/urls/cycle.py +++ b/apiserver/plane/api/urls/cycle.py @@ -7,6 +7,7 @@ from plane.api.views import ( CycleDateCheckEndpoint, CycleFavoriteViewSet, TransferCycleIssueEndpoint, + CycleIssueGroupedEndpoint, ) @@ -43,6 +44,11 @@ urlpatterns = [ ), name="project-issue-cycle", ), + path( + "v3/workspaces//projects//cycles//cycle-issues/", + CycleIssueGroupedEndpoint.as_view(), + name="project-issue-cycle", + ), path( "workspaces//projects//cycles//cycle-issues//", CycleIssueViewSet.as_view( diff --git a/apiserver/plane/api/urls/module.py b/apiserver/plane/api/urls/module.py index 3239af1e4..d9ca849ed 100644 --- a/apiserver/plane/api/urls/module.py +++ b/apiserver/plane/api/urls/module.py @@ -7,6 +7,7 @@ from plane.api.views import ( ModuleLinkViewSet, ModuleFavoriteViewSet, BulkImportModulesEndpoint, + ModuleIssueGroupedEndpoint, ) @@ -43,6 +44,11 @@ urlpatterns = [ ), name="project-module-issues", ), + path( + "v3/workspaces//projects//modules//module-issues/", + ModuleIssueGroupedEndpoint.as_view(), + name="project-issue-cycle", + ), path( "workspaces//projects//modules//module-issues//", ModuleIssueViewSet.as_view( diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 46e88b0bc..12b569523 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -60,6 +60,7 @@ from .cycle import ( CycleDateCheckEndpoint, CycleFavoriteViewSet, TransferCycleIssueEndpoint, + CycleIssueGroupedEndpoint, ) from .asset import FileAssetEndpoint, UserAssetsEndpoint from .issue import ( @@ -113,6 +114,7 @@ from .module import ( ModuleIssueViewSet, ModuleLinkViewSet, ModuleFavoriteViewSet, + ModuleIssueGroupedEndpoint, ) from .api import ApiTokenEndpoint diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index 2a62ab8ac..06df22077 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -707,6 +707,56 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet): return Response(status=status.HTTP_204_NO_CONTENT) +class CycleIssueGroupedEndpoint(BaseAPIView): + + permission_classes = [ + ProjectEntityPermission, + ] + + def get(self, request, slug, project_id, cycle_id): + filters = issue_filters(request.query_params, "GET") + fields = [field for field in request.GET.get("fields", "").split(",") if field] + + issues = ( + Issue.issue_objects.filter(issue_cycle__cycle_id=cycle_id) + .annotate( + sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + .annotate(bridge_id=F("issue_cycle__id")) + .filter(project_id=project_id) + .filter(workspace__slug=slug) + .select_related("project") + .select_related("workspace") + .select_related("state") + .select_related("parent") + .prefetch_related("assignees") + .prefetch_related("labels") + .filter(**filters) + .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") + ) + ) + + issues = IssueStateSerializer(issues, 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 CycleDateCheckEndpoint(BaseAPIView): permission_classes = [ ProjectEntityPermission, diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index 173526a2c..f8d74dbed 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -15,7 +15,7 @@ from rest_framework import status from sentry_sdk import capture_exception # Module imports -from . import BaseViewSet, WebhookMixin +from . import BaseViewSet, BaseAPIView, WebhookMixin from plane.api.serializers import ( ModuleWriteSerializer, ModuleSerializer, @@ -481,6 +481,55 @@ class ModuleIssueViewSet(BaseViewSet): return Response(status=status.HTTP_204_NO_CONTENT) +class ModuleIssueGroupedEndpoint(BaseAPIView): + + permission_classes = [ + ProjectEntityPermission, + ] + + def get(self, request, slug, project_id, module_id): + filters = issue_filters(request.query_params, "GET") + fields = [field for field in request.GET.get("fields", "").split(",") if field] + + issues = ( + Issue.issue_objects.filter(issue_module__module_id=module_id) + .annotate( + sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + .annotate(bridge_id=F("issue_module__id")) + .filter(project_id=project_id) + .filter(workspace__slug=slug) + .select_related("project") + .select_related("workspace") + .select_related("state") + .select_related("parent") + .prefetch_related("assignees") + .prefetch_related("labels") + .filter(**filters) + .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") + ) + ) + + issues = IssueStateSerializer(issues, 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 ModuleLinkViewSet(BaseViewSet): permission_classes = [ ProjectEntityPermission,