dev: update the issues endpoint

This commit is contained in:
pablohashescobar 2024-02-16 18:09:10 +05:30
parent 9b3c3a96ef
commit 445d47fe55
3 changed files with 136 additions and 20 deletions

View File

@ -614,10 +614,11 @@ class IssueSerializer(DynamicBaseSerializer):
class IssueDetailSerializer(IssueSerializer): class IssueDetailSerializer(IssueSerializer):
description_html = serializers.CharField() description_html = serializers.CharField()
is_subscribed = serializers.BooleanField()
class Meta(IssueSerializer.Meta): class Meta(IssueSerializer.Meta):
fields = IssueSerializer.Meta.fields + ['description_html'] fields = IssueSerializer.Meta.fields + ['description_html', 'is_subscribed']
class IssueLiteSerializer(DynamicBaseSerializer): class IssueLiteSerializer(DynamicBaseSerializer):

View File

@ -3,7 +3,7 @@ import json
# Django import # Django import
from django.utils import timezone from django.utils import timezone
from django.db.models import Q, Count, OuterRef, Func, F, Prefetch from django.db.models import Q, Count, OuterRef, Func, F, Prefetch, Exists
from django.core.serializers.json import DjangoJSONEncoder from django.core.serializers.json import DjangoJSONEncoder
# Third party imports # Third party imports
@ -21,6 +21,7 @@ from plane.db.models import (
IssueLink, IssueLink,
IssueAttachment, IssueAttachment,
ProjectMember, ProjectMember,
IssueSubscriber,
) )
from plane.app.serializers import ( from plane.app.serializers import (
IssueSerializer, IssueSerializer,
@ -92,7 +93,7 @@ class InboxIssueViewSet(BaseViewSet):
Issue.objects.filter( Issue.objects.filter(
project_id=self.kwargs.get("project_id"), project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"), workspace__slug=self.kwargs.get("slug"),
issue_inbox__inbox_id=self.kwargs.get("inbox_id") issue_inbox__inbox_id=self.kwargs.get("inbox_id"),
) )
.select_related("workspace", "project", "state", "parent") .select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module") .prefetch_related("assignees", "labels", "issue_module__module")
@ -131,8 +132,14 @@ class InboxIssueViewSet(BaseViewSet):
def list(self, request, slug, project_id, inbox_id): def list(self, request, slug, project_id, inbox_id):
filters = issue_filters(request.query_params, "GET") filters = issue_filters(request.query_params, "GET")
issue_queryset = self.get_queryset().filter(**filters).order_by("issue_inbox__snoozed_till", "issue_inbox__status") issue_queryset = (
issues_data = IssueSerializer(issue_queryset, expand=self.expand, many=True).data self.get_queryset()
.filter(**filters)
.order_by("issue_inbox__snoozed_till", "issue_inbox__status")
)
issues_data = IssueSerializer(
issue_queryset, expand=self.expand, many=True
).data
return Response( return Response(
issues_data, issues_data,
status=status.HTTP_200_OK, status=status.HTTP_200_OK,
@ -199,8 +206,8 @@ class InboxIssueViewSet(BaseViewSet):
source=request.data.get("source", "in-app"), source=request.data.get("source", "in-app"),
) )
issue = (self.get_queryset().filter(pk=issue.id).first()) issue = self.get_queryset().filter(pk=issue.id).first()
serializer = IssueSerializer(issue ,expand=self.expand) serializer = IssueSerializer(issue, expand=self.expand)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
def partial_update(self, request, slug, project_id, inbox_id, issue_id): def partial_update(self, request, slug, project_id, inbox_id, issue_id):
@ -320,20 +327,34 @@ class InboxIssueViewSet(BaseViewSet):
if state is not None: if state is not None:
issue.state = state issue.state = state
issue.save() issue.save()
issue = (self.get_queryset().filter(pk=issue_id).first()) issue = self.get_queryset().filter(pk=issue_id).first()
serializer = IssueSerializer(issue, expand=self.expand) serializer = IssueSerializer(issue, expand=self.expand)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
return Response( return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST serializer.errors, status=status.HTTP_400_BAD_REQUEST
) )
else: else:
issue = (self.get_queryset().filter(pk=issue_id).first()) issue = self.get_queryset().filter(pk=issue_id).first()
serializer = IssueSerializer(issue ,expand=self.expand) serializer = IssueSerializer(issue, expand=self.expand)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
def retrieve(self, request, slug, project_id, inbox_id, issue_id): def retrieve(self, request, slug, project_id, inbox_id, issue_id):
issue = self.get_queryset().filter(pk=issue_id).first() issue = (
serializer = IssueSerializer(issue, expand=self.expand,) self.get_queryset()
.filter(pk=issue_id)
.annotate(
is_subscribed=Exists(
IssueSubscriber.objects.filter(
subscriber_id=request.user.id, issue_id=issue_id
)
)
)
.first()
)
serializer = IssueSerializer(
issue,
expand=self.expand,
)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
def destroy(self, request, slug, project_id, inbox_id, issue_id): def destroy(self, request, slug, project_id, inbox_id, issue_id):

View File

@ -267,7 +267,18 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def retrieve(self, request, slug, project_id, pk=None): def retrieve(self, request, slug, project_id, pk=None):
issue = self.get_queryset().filter(pk=pk).first() issue = (
self.get_queryset()
.filter(pk=pk)
.annotate(
is_subscribed=Exists(
IssueSubscriber.objects.filter(
subscriber_id=request.user.id, issue_id=pk
)
)
)
.first()
)
return Response( return Response(
IssueDetailSerializer( IssueDetailSerializer(
issue, fields=self.fields, expand=self.expand issue, fields=self.fields, expand=self.expand
@ -715,7 +726,9 @@ class LabelViewSet(BaseViewSet):
) )
@invalidate_path_cache("/api/workspaces/:slug/labels/") @invalidate_path_cache("/api/workspaces/:slug/labels/")
@invalidate_path_cache("/api/workspaces/:slug/projects/:project_id/issue-labels/") @invalidate_path_cache(
"/api/workspaces/:slug/projects/:project_id/issue-labels/"
)
def create(self, request, slug, project_id): def create(self, request, slug, project_id):
try: try:
serializer = LabelSerializer(data=request.data) serializer = LabelSerializer(data=request.data)
@ -734,22 +747,26 @@ class LabelViewSet(BaseViewSet):
}, },
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
@invalidate_path_cache("/api/workspaces/:slug/labels/") @invalidate_path_cache("/api/workspaces/:slug/labels/")
@invalidate_path_cache("/api/workspaces/:slug/projects/:project_id/issue-labels/") @invalidate_path_cache(
"/api/workspaces/:slug/projects/:project_id/issue-labels/"
)
def partial_update(self, request, *args, **kwargs): def partial_update(self, request, *args, **kwargs):
return super().partial_update(request, *args, **kwargs) return super().partial_update(request, *args, **kwargs)
@invalidate_path_cache("/api/workspaces/:slug/labels/") @invalidate_path_cache("/api/workspaces/:slug/labels/")
@invalidate_path_cache("/api/workspaces/:slug/projects/:project_id/issue-labels/") @invalidate_path_cache(
"/api/workspaces/:slug/projects/:project_id/issue-labels/"
)
def destroy(self, request, *args, **kwargs): def destroy(self, request, *args, **kwargs):
return super().destroy(request, *args, **kwargs) return super().destroy(request, *args, **kwargs)
@cache_path_response(60 * 60 * 2) @cache_path_response(60 * 60 * 2)
def list(self, request, *args, **kwargs): def list(self, request, *args, **kwargs):
return super().list(request, *args, **kwargs) return super().list(request, *args, **kwargs)
class BulkDeleteIssuesEndpoint(BaseAPIView): class BulkDeleteIssuesEndpoint(BaseAPIView):
permission_classes = [ permission_classes = [
ProjectEntityPermission, ProjectEntityPermission,
@ -1964,5 +1981,82 @@ class IssueListEndpoint(BaseAPIView):
.values("count") .values("count")
) )
).distinct() ).distinct()
serializer = IssueSerializer(queryset)
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)
# 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)
serializer = IssueSerializer(queryset, many=True, fields=self.fields, expand=self.expand)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)