From e6e2b4e096b830a00610cfc8c438675870415bd8 Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Tue, 19 Sep 2023 13:47:29 +0530 Subject: [PATCH] fix: filters and group up by for workspace issues --- apiserver/plane/api/urls.py | 8 +- apiserver/plane/api/views/__init__.py | 2 +- apiserver/plane/api/views/view.py | 179 ++++++++++++++++++++------ 3 files changed, 149 insertions(+), 40 deletions(-) diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index 9d5a5b548..b9c5cc6b3 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -103,7 +103,7 @@ from plane.api.views import ( ## End Estimates # Views WorkspaceViewViewSet, - WorkspaceViewIssuesEndpoint, + WorkspaceViewIssuesViewSet, IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet, @@ -675,7 +675,11 @@ urlpatterns = [ ), path( "workspaces//views//issues/", - WorkspaceViewIssuesEndpoint.as_view(), + WorkspaceViewIssuesViewSet.as_view( + { + "get": "list", + } + ), name="workspace-view-issues", ), path( diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 219c5367f..f35ae4e76 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -56,7 +56,7 @@ from .workspace import ( LeaveWorkspaceEndpoint, ) from .state import StateViewSet -from .view import WorkspaceViewViewSet, WorkspaceViewIssuesEndpoint, IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet +from .view import WorkspaceViewViewSet, WorkspaceViewIssuesViewSet, IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet from .cycle import ( CycleViewSet, CycleIssueViewSet, diff --git a/apiserver/plane/api/views/view.py b/apiserver/plane/api/views/view.py index 95a483dd4..726d0fcf1 100644 --- a/apiserver/plane/api/views/view.py +++ b/apiserver/plane/api/views/view.py @@ -1,4 +1,18 @@ # Django imports +from django.db.models import ( + Prefetch, + OuterRef, + Func, + F, + Case, + Value, + CharField, + When, + Exists, + Max, +) +from django.utils.decorators import method_decorator +from django.views.decorators.gzip import gzip_page from django.db import IntegrityError from django.db.models import Prefetch, OuterRef, Exists @@ -23,8 +37,11 @@ from plane.db.models import ( Issue, IssueViewFavorite, IssueReaction, + IssueLink, + IssueAttachment, ) from plane.utils.issue_filters import issue_filters +from plane.utils.grouper import group_results class WorkspaceViewViewSet(BaseViewSet): @@ -49,55 +66,143 @@ class WorkspaceViewViewSet(BaseViewSet): ) -class WorkspaceViewIssuesEndpoint(BaseAPIView): +class WorkspaceViewIssuesViewSet(BaseViewSet): permission_classes = [ WorkspaceEntityPermission, ] - def get(self, request, slug, view_id): - try: - view = WorkspaceView.objects.get(pk=view_id) - queries = view.query - - filters = issue_filters(request.query_params, "GET") - - issues = ( - Issue.issue_objects.filter( - **queries, - workspace__slug=slug, - project__project_projectmember__member=self.request.user - ) - .filter(**filters) - .select_related("workspace") - .select_related("state") - .select_related("parent") - .prefetch_related("assignees") - .prefetch_related("labels") - .prefetch_related( - Prefetch( - "issue_reactions", - queryset=IssueReaction.objects.select_related("actor"), - ) + def get_queryset(self): + return ( + Issue.issue_objects.annotate( + sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + .filter(workspace__slug=self.kwargs.get("slug")) + .select_related("workspace") + .select_related("state") + .select_related("parent") + .prefetch_related("assignees") + .prefetch_related("labels") + .prefetch_related( + Prefetch( + "issue_reactions", + queryset=IssueReaction.objects.select_related("actor"), ) ) - if request.GET.get("per_page", False) and request.GET.get("cursor", False): - return self.paginate( - request=request, - queryset=issues, - on_results=lambda issues: IssueLiteSerializer( - issues, many=True - ).data, + ) + + + @method_decorator(gzip_page) + def list(self, request, slug, view_id): + try: + filters = issue_filters(request.query_params, "GET") + + # Custom ordering for priority and state + priority_order = ["urgent", "high", "medium", "low", "none"] + state_order = ["backlog", "unstarted", "started", "completed", "cancelled"] + + order_by_param = request.GET.get("order_by", "-created_at") + + issue_queryset = ( + self.get_queryset() + .filter(**filters) + .filter(project__project_projectmember__member=self.request.user) + .annotate(cycle_id=F("issue_cycle__cycle_id")) + .annotate(module_id=F("issue_module__module_id")) + .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") + ) + ) + + # Priority Ordering + if order_by_param == "priority" or order_by_param == "-priority": + priority_order = ( + priority_order + if order_by_param == "priority" + else priority_order[::-1] + ) + issue_queryset = issue_queryset.annotate( + priority_order=Case( + *[ + When(priority=p, then=Value(i)) + for i, p in enumerate(priority_order) + ], + output_field=CharField(), + ) + ).order_by("priority_order") + + # State Ordering + elif order_by_param in [ + "state__name", + "state__group", + "-state__name", + "-state__group", + ]: + state_order = ( + state_order + if order_by_param in ["state__name", "state__group"] + else state_order[::-1] + ) + issue_queryset = issue_queryset.annotate( + state_order=Case( + *[ + When(state__group=state_group, then=Value(i)) + for i, state_group in enumerate(state_order) + ], + default=Value(len(state_order)), + output_field=CharField(), + ) + ).order_by("state_order") + # assignee and label ordering + elif order_by_param in [ + "labels__name", + "-labels__name", + "assignees__first_name", + "-assignees__first_name", + ]: + issue_queryset = issue_queryset.annotate( + max_values=Max( + order_by_param[1::] + if order_by_param.startswith("-") + else order_by_param + ) + ).order_by( + "-max_values" if order_by_param.startswith("-") else "max_values" ) else: + issue_queryset = issue_queryset.order_by(order_by_param) + + issues = IssueLiteSerializer(issue_queryset, many=True).data + + ## Grouping the results + group_by = request.GET.get("group_by", False) + sub_group_by = request.GET.get("sub_group_by", False) + if sub_group_by and sub_group_by == group_by: return Response( - {"error": "per_page and cursor are required"}, + {"error": "Group by and sub group by cannot be same"}, status=status.HTTP_400_BAD_REQUEST, ) - except WorkspaceView.DoesNotExist: - return Response( - {"error": "Workspace View does not exist"}, status=status.HTTP_404_NOT_FOUND - ) + if group_by: + return Response( + group_results(issues, group_by, sub_group_by), status=status.HTTP_200_OK + ) + + return Response(issues, status=status.HTTP_200_OK) + except Exception as e: capture_exception(e) return Response(