diff --git a/apiserver/plane/app/views/issue.py b/apiserver/plane/app/views/issue.py index 25c42dc5b..daa093c8c 100644 --- a/apiserver/plane/app/views/issue.py +++ b/apiserver/plane/app/views/issue.py @@ -2,6 +2,7 @@ import json import random from itertools import chain +from collections import defaultdict # Django imports from django.utils import timezone @@ -11,12 +12,7 @@ from django.db.models import ( Func, F, Q, - Case, - Value, - CharField, - When, Exists, - Max, ) from django.core.serializers.json import DjangoJSONEncoder from django.utils.decorators import method_decorator @@ -77,7 +73,7 @@ from plane.db.models import ( from plane.bgtasks.issue_activites_task import issue_activity from plane.utils.grouper import group_results from plane.utils.issue_filters import issue_filters -from collections import defaultdict +from plane.utils.order_queryset import order_issue_queryset class IssueListEndpoint(BaseAPIView): @@ -95,7 +91,9 @@ class IssueListEndpoint(BaseAPIView): status=status.HTTP_400_BAD_REQUEST, ) - issue_ids = [issue_id for issue_id in issue_ids.split(",") if issue_id != ""] + issue_ids = [ + issue_id for issue_id in issue_ids.split(",") if issue_id != "" + ] queryset = ( Issue.issue_objects.filter( @@ -157,83 +155,21 @@ class IssueListEndpoint(BaseAPIView): 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 = queryset.filter(**filters) - # 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) + # Issue queryset + issue_queryset = order_issue_queryset( + issue_queryset=issue_queryset, + order_by_param=order_by_param, + ) if self.fields or self.expand: issues = IssueSerializer( - queryset, many=True, fields=self.fields, expand=self.expand + issue_queryset, + many=True, + fields=self.fields, + expand=self.expand, ).data else: issues = issue_queryset.values( @@ -356,77 +292,14 @@ class IssueViewSet(WebhookMixin, BaseViewSet): issue_queryset = self.get_queryset().filter(**filters) # Custom ordering for priority and state - priority_order = ["urgent", "high", "medium", "low", "none"] - state_order = [ - "backlog", - "unstarted", - "started", - "completed", - "cancelled", - ] - # 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") + # Issue queryset + issue_queryset = order_issue_queryset( + issue_queryset=issue_queryset, + order_by_param=order_by_param, + ) - # 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) - - # Only use serializer when expand or fields else return by values - if self.expand or self.fields: + if self.fields or self.expand: issues = IssueSerializer( issue_queryset, many=True, @@ -630,15 +503,6 @@ class UserWorkSpaceIssues(BaseAPIView): def get(self, request, slug): 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 = ( @@ -682,65 +546,9 @@ class UserWorkSpaceIssues(BaseAPIView): .filter(**filters) ).distinct() - # 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) + issue_queryset = order_issue_queryset( + issue_queryset=issue_queryset, order_by_param=order_by_param + ) issues = IssueLiteSerializer(issue_queryset, many=True).data @@ -1475,79 +1283,13 @@ class IssueArchiveViewSet(BaseViewSet): filters = issue_filters(request.query_params, "GET") show_sub_issues = request.GET.get("show_sub_issues", "true") - # 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) - # 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) + issue_queryset = order_issue_queryset( + issue_queryset=issue_queryset, order_by_param=order_by_param + ) issue_queryset = ( issue_queryset @@ -2152,80 +1894,12 @@ class IssueDraftViewSet(BaseViewSet): if field ] - # 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) - - # 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) - + issue_queryset = order_issue_queryset( + issue_queryset=issue_queryset, order_by_param=order_by_param + ) # Only use serializer when expand else return by values if self.expand or self.fields: issues = IssueSerializer( diff --git a/apiserver/plane/app/views/view.py b/apiserver/plane/app/views/view.py index 97a0f036f..91829cf3b 100644 --- a/apiserver/plane/app/views/view.py +++ b/apiserver/plane/app/views/view.py @@ -45,6 +45,7 @@ from plane.db.models import ( IssueAttachment, ) from plane.utils.issue_filters import issue_filters +from plane.utils.order_queryset import order_issue_queryset class GlobalViewViewSet(BaseViewSet): @@ -142,22 +143,6 @@ class GlobalViewIssuesViewSet(BaseViewSet): @method_decorator(gzip_page) def list(self, request, slug): filters = issue_filters(request.query_params, "GET") - fields = [ - field - for field in request.GET.get("fields", "").split(",") - if field - ] - - # 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 = ( @@ -166,66 +151,9 @@ class GlobalViewIssuesViewSet(BaseViewSet): .filter(project__project_projectmember__member=self.request.user) .annotate(cycle_id=F("issue_cycle__cycle_id")) ) - - # 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) + issue_queryset = order_issue_queryset( + issue_queryset=issue_queryset, order_by_param=order_by_param + ) if self.fields: issues = IssueSerializer( diff --git a/apiserver/plane/app/views/workspace.py b/apiserver/plane/app/views/workspace.py index 6677b4c4b..934d469b1 100644 --- a/apiserver/plane/app/views/workspace.py +++ b/apiserver/plane/app/views/workspace.py @@ -74,7 +74,6 @@ from plane.db.models import ( Label, WorkspaceMember, CycleIssue, - IssueReaction, WorkspaceUserProperties, Estimate, EstimatePoint, @@ -88,7 +87,6 @@ from plane.app.permissions import ( WorkspaceEntityPermission, WorkspaceViewerPermission, WorkspaceUserPermission, - ProjectLitePermission, ) from plane.bgtasks.workspace_invitation_task import workspace_invitation from plane.utils.issue_filters import issue_filters @@ -1337,16 +1335,6 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView): ] 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 = ( Issue.issue_objects.filter( @@ -1411,65 +1399,9 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView): .order_by("created_at") ).distinct() - # 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) + issue_queryset = order_by_param( + issue_queryset=issue_queryset, order_by_param=order_by_param + ) issues = IssueSerializer( issue_queryset, many=True, fields=fields if fields else None diff --git a/apiserver/plane/utils/order_queryset.py b/apiserver/plane/utils/order_queryset.py new file mode 100644 index 000000000..dbaf45832 --- /dev/null +++ b/apiserver/plane/utils/order_queryset.py @@ -0,0 +1,80 @@ +from django.db.models import ( + Case, + Value, + CharField, + When, + Max, +) + +# Custom ordering for priority and state +PRIORITY_ORDER = ["urgent", "high", "medium", "low", "none"] +STATE_ORDER = [ + "backlog", + "unstarted", + "started", + "completed", + "cancelled", +] + +def order_issue_queryset(issue_queryset, order_by_param="created_at"): + # 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) + + return issue_queryset \ No newline at end of file diff --git a/apiserver/plane/utils/paginator.py b/apiserver/plane/utils/paginator.py index 6b2b49c15..cfeb49363 100644 --- a/apiserver/plane/utils/paginator.py +++ b/apiserver/plane/utils/paginator.py @@ -1,5 +1,5 @@ from rest_framework.response import Response -from rest_framework.exceptions import ParseError +from rest_framework.exceptions import ParseError, ValidationError from collections.abc import Sequence import math @@ -159,6 +159,13 @@ class BasePaginator: ) return per_page + + def get_layout(self, request): + layout = request.GET.get("layout", "list") + if layout not in ["list", "kanban", "spreadsheet", "calendar", "gantt"]: + raise ValidationError(detail="Invalid layout given") + return layout + def paginate( self, @@ -175,6 +182,7 @@ class BasePaginator: ): """Paginate the request""" per_page = self.get_per_page(request, default_per_page, max_per_page) + layout = self.get_layout(request=request) # Convert the cursor value to integer and float from string input_cursor = None