mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
[WEB - 467] perf: issues listing endpoints (#3710)
* dev: update issue listing apis * dev: optimize issue listing apis * fix: sub issues endpoint * add loading state for subscription in issueDetail * fix: import error --------- Co-authored-by: rahulramesha <rahulramesham@gmail.com>
This commit is contained in:
parent
ac6e710623
commit
370decc8aa
@ -503,9 +503,7 @@ class IssueCommentSerializer(BaseSerializer):
|
|||||||
workspace_detail = WorkspaceLiteSerializer(
|
workspace_detail = WorkspaceLiteSerializer(
|
||||||
read_only=True, source="workspace"
|
read_only=True, source="workspace"
|
||||||
)
|
)
|
||||||
comment_reactions = CommentReactionSerializer(
|
comment_reactions = CommentReactionSerializer(read_only=True, many=True)
|
||||||
read_only=True, many=True
|
|
||||||
)
|
|
||||||
is_member = serializers.BooleanField(read_only=True)
|
is_member = serializers.BooleanField(read_only=True)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
@ -558,18 +556,17 @@ class IssueStateSerializer(DynamicBaseSerializer):
|
|||||||
|
|
||||||
class IssueSerializer(DynamicBaseSerializer):
|
class IssueSerializer(DynamicBaseSerializer):
|
||||||
# ids
|
# ids
|
||||||
project_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
|
||||||
state_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
|
||||||
parent_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
|
||||||
cycle_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
cycle_id = serializers.PrimaryKeyRelatedField(read_only=True)
|
||||||
module_ids = serializers.SerializerMethodField()
|
module_ids = serializers.ListField(
|
||||||
|
child=serializers.UUIDField(), required=False, allow_null=True
|
||||||
|
)
|
||||||
|
|
||||||
# Many to many
|
# Many to many
|
||||||
label_ids = serializers.PrimaryKeyRelatedField(
|
label_ids = serializers.ListField(
|
||||||
read_only=True, many=True, source="labels"
|
child=serializers.UUIDField(), required=False, allow_null=True
|
||||||
)
|
)
|
||||||
assignee_ids = serializers.PrimaryKeyRelatedField(
|
assignee_ids = serializers.ListField(
|
||||||
read_only=True, many=True, source="assignees"
|
child=serializers.UUIDField(), required=False, allow_null=True
|
||||||
)
|
)
|
||||||
|
|
||||||
# Count items
|
# Count items
|
||||||
@ -577,9 +574,6 @@ class IssueSerializer(DynamicBaseSerializer):
|
|||||||
attachment_count = serializers.IntegerField(read_only=True)
|
attachment_count = serializers.IntegerField(read_only=True)
|
||||||
link_count = serializers.IntegerField(read_only=True)
|
link_count = serializers.IntegerField(read_only=True)
|
||||||
|
|
||||||
# is_subscribed
|
|
||||||
is_subscribed = serializers.BooleanField(read_only=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Issue
|
model = Issue
|
||||||
fields = [
|
fields = [
|
||||||
@ -606,22 +600,19 @@ class IssueSerializer(DynamicBaseSerializer):
|
|||||||
"updated_by",
|
"updated_by",
|
||||||
"attachment_count",
|
"attachment_count",
|
||||||
"link_count",
|
"link_count",
|
||||||
"is_subscribed",
|
|
||||||
"is_draft",
|
"is_draft",
|
||||||
"archived_at",
|
"archived_at",
|
||||||
]
|
]
|
||||||
read_only_fields = fields
|
read_only_fields = fields
|
||||||
|
|
||||||
def get_module_ids(self, obj):
|
|
||||||
# Access the prefetched modules and extract module IDs
|
|
||||||
return [module for module in obj.issue_module.values_list("module_id", flat=True)]
|
|
||||||
|
|
||||||
|
|
||||||
class IssueDetailSerializer(IssueSerializer):
|
class IssueDetailSerializer(IssueSerializer):
|
||||||
description_html = serializers.CharField()
|
description_html = serializers.CharField()
|
||||||
|
is_subscribed = serializers.BooleanField(read_only=True)
|
||||||
|
|
||||||
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):
|
||||||
|
@ -90,11 +90,13 @@ urlpatterns = [
|
|||||||
BulkImportIssuesEndpoint.as_view(),
|
BulkImportIssuesEndpoint.as_view(),
|
||||||
name="project-issues-bulk",
|
name="project-issues-bulk",
|
||||||
),
|
),
|
||||||
|
# deprecated endpoint TODO: remove once confirmed
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/my-issues/",
|
"workspaces/<str:slug>/my-issues/",
|
||||||
UserWorkSpaceIssues.as_view(),
|
UserWorkSpaceIssues.as_view(),
|
||||||
name="workspace-issues",
|
name="workspace-issues",
|
||||||
),
|
),
|
||||||
|
##
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/sub-issues/",
|
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/<uuid:issue_id>/sub-issues/",
|
||||||
SubIssuesEndpoint.as_view(),
|
SubIssuesEndpoint.as_view(),
|
||||||
|
@ -708,10 +708,11 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
]
|
]
|
||||||
order_by = request.GET.get("order_by", "created_at")
|
order_by = request.GET.get("order_by", "created_at")
|
||||||
filters = issue_filters(request.query_params, "GET")
|
filters = issue_filters(request.query_params, "GET")
|
||||||
issues = (
|
queryset = (
|
||||||
Issue.issue_objects.filter(issue_cycle__cycle_id=cycle_id)
|
Issue.issue_objects.filter(issue_cycle__cycle_id=cycle_id)
|
||||||
.filter(project_id=project_id)
|
.filter(project_id=project_id)
|
||||||
.filter(workspace__slug=slug)
|
.filter(workspace__slug=slug)
|
||||||
|
.filter(**filters)
|
||||||
.select_related("workspace", "project", "state", "parent")
|
.select_related("workspace", "project", "state", "parent")
|
||||||
.prefetch_related(
|
.prefetch_related(
|
||||||
"assignees",
|
"assignees",
|
||||||
@ -721,7 +722,6 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
)
|
)
|
||||||
.order_by(order_by)
|
.order_by(order_by)
|
||||||
.filter(**filters)
|
.filter(**filters)
|
||||||
.annotate(module_ids=F("issue_module__module_id"))
|
|
||||||
.annotate(cycle_id=F("issue_cycle__cycle_id"))
|
.annotate(cycle_id=F("issue_cycle__cycle_id"))
|
||||||
.annotate(
|
.annotate(
|
||||||
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
||||||
@ -745,11 +745,67 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
.annotate(count=Func(F("id"), function="Count"))
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
.values("count")
|
.values("count")
|
||||||
)
|
)
|
||||||
|
.annotate(
|
||||||
|
label_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"labels__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(labels__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
assignee_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"assignees__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(assignees__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
module_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"issue_module__module_id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(issue_module__module_id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.order_by(order_by)
|
||||||
)
|
)
|
||||||
serializer = IssueSerializer(
|
if self.fields:
|
||||||
issues, many=True, fields=fields if fields else None
|
issues = IssueSerializer(
|
||||||
)
|
queryset, many=True, fields=fields if fields else None
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
).data
|
||||||
|
else:
|
||||||
|
issues = queryset.values(
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"state_id",
|
||||||
|
"sort_order",
|
||||||
|
"completed_at",
|
||||||
|
"estimate_point",
|
||||||
|
"priority",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"sequence_id",
|
||||||
|
"project_id",
|
||||||
|
"parent_id",
|
||||||
|
"cycle_id",
|
||||||
|
"module_ids",
|
||||||
|
"label_ids",
|
||||||
|
"assignee_ids",
|
||||||
|
"sub_issues_count",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"created_by",
|
||||||
|
"updated_by",
|
||||||
|
"attachment_count",
|
||||||
|
"link_count",
|
||||||
|
"is_draft",
|
||||||
|
"archived_at",
|
||||||
|
)
|
||||||
|
return Response(issues, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def create(self, request, slug, project_id, cycle_id):
|
def create(self, request, slug, project_id, cycle_id):
|
||||||
issues = request.data.get("issues", [])
|
issues = request.data.get("issues", [])
|
||||||
@ -1121,6 +1177,21 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||||||
)
|
)
|
||||||
.order_by("label_name")
|
.order_by("label_name")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assignee_distribution_data = [
|
||||||
|
{
|
||||||
|
"display_name": item["display_name"],
|
||||||
|
"assignee_id": (
|
||||||
|
str(item["assignee_id"]) if item["assignee_id"] else None
|
||||||
|
),
|
||||||
|
"avatar": item["avatar"],
|
||||||
|
"total_issues": item["total_issues"],
|
||||||
|
"completed_issues": item["completed_issues"],
|
||||||
|
"pending_issues": item["pending_issues"],
|
||||||
|
}
|
||||||
|
for item in assignee_distribution
|
||||||
|
]
|
||||||
|
|
||||||
# Label distribution serilization
|
# Label distribution serilization
|
||||||
label_distribution_data = [
|
label_distribution_data = [
|
||||||
{
|
{
|
||||||
|
@ -5,6 +5,10 @@ import json
|
|||||||
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
|
||||||
from django.core.serializers.json import DjangoJSONEncoder
|
from django.core.serializers.json import DjangoJSONEncoder
|
||||||
|
from django.contrib.postgres.aggregates import ArrayAgg
|
||||||
|
from django.contrib.postgres.fields import ArrayField
|
||||||
|
from django.db.models import Value, UUIDField
|
||||||
|
from django.db.models.functions import Coalesce
|
||||||
|
|
||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@ -92,7 +96,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")
|
||||||
@ -127,14 +131,75 @@ class InboxIssueViewSet(BaseViewSet):
|
|||||||
.annotate(count=Func(F("id"), function="Count"))
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
.values("count")
|
.values("count")
|
||||||
)
|
)
|
||||||
|
.annotate(
|
||||||
|
label_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"labels__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(labels__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
assignee_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"assignees__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(assignees__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
module_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"issue_module__module_id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(issue_module__module_id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
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")
|
||||||
|
)
|
||||||
|
if self.expand:
|
||||||
|
issues = IssueSerializer(
|
||||||
|
issue_queryset, expand=self.expand, many=True
|
||||||
|
).data
|
||||||
|
else:
|
||||||
|
issues = issue_queryset.values(
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"state_id",
|
||||||
|
"sort_order",
|
||||||
|
"completed_at",
|
||||||
|
"estimate_point",
|
||||||
|
"priority",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"sequence_id",
|
||||||
|
"project_id",
|
||||||
|
"parent_id",
|
||||||
|
"cycle_id",
|
||||||
|
"module_ids",
|
||||||
|
"label_ids",
|
||||||
|
"assignee_ids",
|
||||||
|
"sub_issues_count",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"created_by",
|
||||||
|
"updated_by",
|
||||||
|
"attachment_count",
|
||||||
|
"link_count",
|
||||||
|
"is_draft",
|
||||||
|
"archived_at",
|
||||||
|
)
|
||||||
return Response(
|
return Response(
|
||||||
issues_data,
|
issues,
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -199,8 +264,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 +385,23 @@ 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 = self.get_queryset().filter(pk=issue_id).first()
|
||||||
serializer = IssueDetailSerializer(issue, expand=self.expand,)
|
serializer = IssueDetailSerializer(
|
||||||
|
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):
|
||||||
|
@ -298,12 +298,6 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||||
.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")
|
||||||
.prefetch_related(
|
|
||||||
Prefetch(
|
|
||||||
"issue_reactions",
|
|
||||||
queryset=IssueReaction.objects.select_related("actor"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.annotate(cycle_id=F("issue_cycle__cycle_id"))
|
.annotate(cycle_id=F("issue_cycle__cycle_id"))
|
||||||
.annotate(
|
.annotate(
|
||||||
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
||||||
@ -327,12 +321,40 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
.annotate(count=Func(F("id"), function="Count"))
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
.values("count")
|
.values("count")
|
||||||
)
|
)
|
||||||
|
.annotate(
|
||||||
|
label_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"labels__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(labels__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
assignee_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"assignees__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(assignees__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
module_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"issue_module__module_id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(issue_module__module_id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
@method_decorator(gzip_page)
|
@method_decorator(gzip_page)
|
||||||
def list(self, request, slug, project_id):
|
def list(self, request, slug, project_id):
|
||||||
filters = issue_filters(request.query_params, "GET")
|
filters = issue_filters(request.query_params, "GET")
|
||||||
|
order_by_param = request.GET.get("order_by", "-created_at")
|
||||||
|
|
||||||
|
issue_queryset = self.get_queryset().filter(**filters)
|
||||||
# Custom ordering for priority and state
|
# Custom ordering for priority and state
|
||||||
priority_order = ["urgent", "high", "medium", "low", "none"]
|
priority_order = ["urgent", "high", "medium", "low", "none"]
|
||||||
state_order = [
|
state_order = [
|
||||||
@ -343,10 +365,6 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
"cancelled",
|
"cancelled",
|
||||||
]
|
]
|
||||||
|
|
||||||
order_by_param = request.GET.get("order_by", "-created_at")
|
|
||||||
|
|
||||||
issue_queryset = self.get_queryset().filter(**filters)
|
|
||||||
|
|
||||||
# Priority Ordering
|
# Priority Ordering
|
||||||
if order_by_param == "priority" or order_by_param == "-priority":
|
if order_by_param == "priority" or order_by_param == "-priority":
|
||||||
priority_order = (
|
priority_order = (
|
||||||
@ -407,9 +425,42 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
else:
|
else:
|
||||||
issue_queryset = issue_queryset.order_by(order_by_param)
|
issue_queryset = issue_queryset.order_by(order_by_param)
|
||||||
|
|
||||||
issues = IssueSerializer(
|
# Only use serializer when expand or fields else return by values
|
||||||
issue_queryset, many=True, fields=self.fields, expand=self.expand
|
if self.expand or self.fields:
|
||||||
).data
|
issues = IssueSerializer(
|
||||||
|
issue_queryset,
|
||||||
|
many=True,
|
||||||
|
fields=self.fields,
|
||||||
|
expand=self.expand,
|
||||||
|
).data
|
||||||
|
else:
|
||||||
|
issues = issue_queryset.values(
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"state_id",
|
||||||
|
"sort_order",
|
||||||
|
"completed_at",
|
||||||
|
"estimate_point",
|
||||||
|
"priority",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"sequence_id",
|
||||||
|
"project_id",
|
||||||
|
"parent_id",
|
||||||
|
"cycle_id",
|
||||||
|
"module_ids",
|
||||||
|
"label_ids",
|
||||||
|
"assignee_ids",
|
||||||
|
"sub_issues_count",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"created_by",
|
||||||
|
"updated_by",
|
||||||
|
"attachment_count",
|
||||||
|
"link_count",
|
||||||
|
"is_draft",
|
||||||
|
"archived_at",
|
||||||
|
)
|
||||||
return Response(issues, status=status.HTTP_200_OK)
|
return Response(issues, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def create(self, request, slug, project_id):
|
def create(self, request, slug, project_id):
|
||||||
@ -442,10 +493,38 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
origin=request.META.get("HTTP_ORIGIN"),
|
origin=request.META.get("HTTP_ORIGIN"),
|
||||||
)
|
)
|
||||||
issue = (
|
issue = (
|
||||||
self.get_queryset().filter(pk=serializer.data["id"]).first()
|
self.get_queryset()
|
||||||
|
.filter(pk=serializer.data["id"])
|
||||||
|
.values(
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"state_id",
|
||||||
|
"sort_order",
|
||||||
|
"completed_at",
|
||||||
|
"estimate_point",
|
||||||
|
"priority",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"sequence_id",
|
||||||
|
"project_id",
|
||||||
|
"parent_id",
|
||||||
|
"cycle_id",
|
||||||
|
"module_ids",
|
||||||
|
"label_ids",
|
||||||
|
"assignee_ids",
|
||||||
|
"sub_issues_count",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"created_by",
|
||||||
|
"updated_by",
|
||||||
|
"attachment_count",
|
||||||
|
"link_count",
|
||||||
|
"is_draft",
|
||||||
|
"archived_at",
|
||||||
|
)
|
||||||
|
.first()
|
||||||
)
|
)
|
||||||
serializer = IssueSerializer(issue)
|
return Response(issue, status=status.HTTP_201_CREATED)
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
|
||||||
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):
|
||||||
@ -481,19 +560,45 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
notification=True,
|
notification=True,
|
||||||
origin=request.META.get("HTTP_ORIGIN"),
|
origin=request.META.get("HTTP_ORIGIN"),
|
||||||
)
|
)
|
||||||
issue = self.get_queryset().filter(pk=pk).first()
|
issue = (
|
||||||
return Response(
|
self.get_queryset()
|
||||||
IssueSerializer(issue).data, status=status.HTTP_200_OK
|
.filter(pk=pk)
|
||||||
|
.values(
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"state_id",
|
||||||
|
"sort_order",
|
||||||
|
"completed_at",
|
||||||
|
"estimate_point",
|
||||||
|
"priority",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"sequence_id",
|
||||||
|
"project_id",
|
||||||
|
"parent_id",
|
||||||
|
"cycle_id",
|
||||||
|
"module_ids",
|
||||||
|
"label_ids",
|
||||||
|
"assignee_ids",
|
||||||
|
"sub_issues_count",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"created_by",
|
||||||
|
"updated_by",
|
||||||
|
"attachment_count",
|
||||||
|
"link_count",
|
||||||
|
"is_draft",
|
||||||
|
"archived_at",
|
||||||
|
)
|
||||||
|
.first()
|
||||||
)
|
)
|
||||||
|
return Response(issue, status=status.HTTP_200_OK)
|
||||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
def destroy(self, request, slug, project_id, pk=None):
|
def destroy(self, request, slug, project_id, pk=None):
|
||||||
issue = Issue.objects.get(
|
issue = Issue.objects.get(
|
||||||
workspace__slug=slug, project_id=project_id, pk=pk
|
workspace__slug=slug, project_id=project_id, pk=pk
|
||||||
)
|
)
|
||||||
current_instance = json.dumps(
|
|
||||||
IssueSerializer(issue).data, cls=DjangoJSONEncoder
|
|
||||||
)
|
|
||||||
issue.delete()
|
issue.delete()
|
||||||
issue_activity.delay(
|
issue_activity.delay(
|
||||||
type="issue.activity.deleted",
|
type="issue.activity.deleted",
|
||||||
@ -501,7 +606,7 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
actor_id=str(request.user.id),
|
actor_id=str(request.user.id),
|
||||||
issue_id=str(pk),
|
issue_id=str(pk),
|
||||||
project_id=str(project_id),
|
project_id=str(project_id),
|
||||||
current_instance=current_instance,
|
current_instance={},
|
||||||
epoch=int(timezone.now().timestamp()),
|
epoch=int(timezone.now().timestamp()),
|
||||||
notification=True,
|
notification=True,
|
||||||
origin=request.META.get("HTTP_ORIGIN"),
|
origin=request.META.get("HTTP_ORIGIN"),
|
||||||
@ -509,6 +614,7 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: deprecated remove once confirmed
|
||||||
class UserWorkSpaceIssues(BaseAPIView):
|
class UserWorkSpaceIssues(BaseAPIView):
|
||||||
@method_decorator(gzip_page)
|
@method_decorator(gzip_page)
|
||||||
def get(self, request, slug):
|
def get(self, request, slug):
|
||||||
@ -563,12 +669,6 @@ class UserWorkSpaceIssues(BaseAPIView):
|
|||||||
.annotate(count=Func(F("id"), function="Count"))
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
.values("count")
|
.values("count")
|
||||||
)
|
)
|
||||||
.prefetch_related(
|
|
||||||
Prefetch(
|
|
||||||
"issue_reactions",
|
|
||||||
queryset=IssueReaction.objects.select_related("actor"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.filter(**filters)
|
.filter(**filters)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
@ -653,6 +753,7 @@ class UserWorkSpaceIssues(BaseAPIView):
|
|||||||
return Response(issues, status=status.HTTP_200_OK)
|
return Response(issues, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: deprecated remove once confirmed
|
||||||
class WorkSpaceIssuesEndpoint(BaseAPIView):
|
class WorkSpaceIssuesEndpoint(BaseAPIView):
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
WorkSpaceAdminPermission,
|
WorkSpaceAdminPermission,
|
||||||
@ -955,20 +1056,9 @@ class SubIssuesEndpoint(BaseAPIView):
|
|||||||
Issue.issue_objects.filter(
|
Issue.issue_objects.filter(
|
||||||
parent_id=issue_id, workspace__slug=slug
|
parent_id=issue_id, workspace__slug=slug
|
||||||
)
|
)
|
||||||
.select_related("project")
|
.select_related("workspace", "project", "state", "parent")
|
||||||
.select_related("workspace")
|
.prefetch_related("assignees", "labels", "issue_module__module")
|
||||||
.select_related("state")
|
.annotate(cycle_id=F("issue_cycle__cycle_id"))
|
||||||
.select_related("parent")
|
|
||||||
.prefetch_related("assignees")
|
|
||||||
.prefetch_related("labels")
|
|
||||||
.annotate(
|
|
||||||
sub_issues_count=Issue.issue_objects.filter(
|
|
||||||
parent=OuterRef("id")
|
|
||||||
)
|
|
||||||
.order_by()
|
|
||||||
.annotate(count=Func(F("id"), function="Count"))
|
|
||||||
.values("count")
|
|
||||||
)
|
|
||||||
.annotate(
|
.annotate(
|
||||||
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
||||||
.order_by()
|
.order_by()
|
||||||
@ -983,11 +1073,39 @@ class SubIssuesEndpoint(BaseAPIView):
|
|||||||
.annotate(count=Func(F("id"), function="Count"))
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
.values("count")
|
.values("count")
|
||||||
)
|
)
|
||||||
.prefetch_related(
|
.annotate(
|
||||||
Prefetch(
|
sub_issues_count=Issue.issue_objects.filter(
|
||||||
"issue_reactions",
|
parent=OuterRef("id")
|
||||||
queryset=IssueReaction.objects.select_related("actor"),
|
|
||||||
)
|
)
|
||||||
|
.order_by()
|
||||||
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
|
.values("count")
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
label_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"labels__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(labels__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
assignee_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"assignees__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(assignees__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
module_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"issue_module__module_id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(issue_module__module_id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
.annotate(state_group=F("state__group"))
|
.annotate(state_group=F("state__group"))
|
||||||
)
|
)
|
||||||
@ -997,13 +1115,36 @@ class SubIssuesEndpoint(BaseAPIView):
|
|||||||
for sub_issue in sub_issues:
|
for sub_issue in sub_issues:
|
||||||
result[sub_issue.state_group].append(str(sub_issue.id))
|
result[sub_issue.state_group].append(str(sub_issue.id))
|
||||||
|
|
||||||
serializer = IssueSerializer(
|
sub_issues = sub_issues.values(
|
||||||
sub_issues,
|
"id",
|
||||||
many=True,
|
"name",
|
||||||
|
"state_id",
|
||||||
|
"sort_order",
|
||||||
|
"completed_at",
|
||||||
|
"estimate_point",
|
||||||
|
"priority",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"sequence_id",
|
||||||
|
"project_id",
|
||||||
|
"parent_id",
|
||||||
|
"cycle_id",
|
||||||
|
"module_ids",
|
||||||
|
"label_ids",
|
||||||
|
"assignee_ids",
|
||||||
|
"sub_issues_count",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"created_by",
|
||||||
|
"updated_by",
|
||||||
|
"attachment_count",
|
||||||
|
"link_count",
|
||||||
|
"is_draft",
|
||||||
|
"archived_at",
|
||||||
)
|
)
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"sub_issues": serializer.data,
|
"sub_issues": sub_issues,
|
||||||
"state_distribution": result,
|
"state_distribution": result,
|
||||||
},
|
},
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
@ -1291,15 +1432,36 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||||||
.annotate(count=Func(F("id"), function="Count"))
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
.values("count")
|
.values("count")
|
||||||
)
|
)
|
||||||
|
.annotate(
|
||||||
|
label_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"labels__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(labels__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
assignee_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"assignees__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(assignees__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
module_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"issue_module__module_id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(issue_module__module_id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@method_decorator(gzip_page)
|
@method_decorator(gzip_page)
|
||||||
def list(self, request, slug, project_id):
|
def list(self, request, slug, project_id):
|
||||||
fields = [
|
|
||||||
field
|
|
||||||
for field in request.GET.get("fields", "").split(",")
|
|
||||||
if field
|
|
||||||
]
|
|
||||||
filters = issue_filters(request.query_params, "GET")
|
filters = issue_filters(request.query_params, "GET")
|
||||||
show_sub_issues = request.GET.get("show_sub_issues", "true")
|
show_sub_issues = request.GET.get("show_sub_issues", "true")
|
||||||
|
|
||||||
@ -1382,10 +1544,40 @@ class IssueArchiveViewSet(BaseViewSet):
|
|||||||
if show_sub_issues == "true"
|
if show_sub_issues == "true"
|
||||||
else issue_queryset.filter(parent__isnull=True)
|
else issue_queryset.filter(parent__isnull=True)
|
||||||
)
|
)
|
||||||
|
if self.expand or self.fields:
|
||||||
issues = IssueSerializer(
|
issues = IssueSerializer(
|
||||||
issue_queryset, many=True, fields=fields if fields else None
|
issue_queryset,
|
||||||
).data
|
many=True,
|
||||||
|
fields=self.fields,
|
||||||
|
).data
|
||||||
|
else:
|
||||||
|
issues = issue_queryset.values(
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"state_id",
|
||||||
|
"sort_order",
|
||||||
|
"completed_at",
|
||||||
|
"estimate_point",
|
||||||
|
"priority",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"sequence_id",
|
||||||
|
"project_id",
|
||||||
|
"parent_id",
|
||||||
|
"cycle_id",
|
||||||
|
"module_ids",
|
||||||
|
"label_ids",
|
||||||
|
"assignee_ids",
|
||||||
|
"sub_issues_count",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"created_by",
|
||||||
|
"updated_by",
|
||||||
|
"attachment_count",
|
||||||
|
"link_count",
|
||||||
|
"is_draft",
|
||||||
|
"archived_at",
|
||||||
|
)
|
||||||
return Response(issues, status=status.HTTP_200_OK)
|
return Response(issues, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def retrieve(self, request, slug, project_id, pk=None):
|
def retrieve(self, request, slug, project_id, pk=None):
|
||||||
@ -1856,12 +2048,6 @@ class IssueDraftViewSet(BaseViewSet):
|
|||||||
.filter(is_draft=True)
|
.filter(is_draft=True)
|
||||||
.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")
|
||||||
.prefetch_related(
|
|
||||||
Prefetch(
|
|
||||||
"issue_reactions",
|
|
||||||
queryset=IssueReaction.objects.select_related("actor"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.annotate(cycle_id=F("issue_cycle__cycle_id"))
|
.annotate(cycle_id=F("issue_cycle__cycle_id"))
|
||||||
.annotate(
|
.annotate(
|
||||||
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
||||||
@ -1885,6 +2071,32 @@ class IssueDraftViewSet(BaseViewSet):
|
|||||||
.annotate(count=Func(F("id"), function="Count"))
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
.values("count")
|
.values("count")
|
||||||
)
|
)
|
||||||
|
.annotate(
|
||||||
|
label_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"labels__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(labels__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
assignee_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"assignees__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(assignees__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
module_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"issue_module__module_id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(issue_module__module_id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
@method_decorator(gzip_page)
|
@method_decorator(gzip_page)
|
||||||
@ -1970,9 +2182,42 @@ class IssueDraftViewSet(BaseViewSet):
|
|||||||
else:
|
else:
|
||||||
issue_queryset = issue_queryset.order_by(order_by_param)
|
issue_queryset = issue_queryset.order_by(order_by_param)
|
||||||
|
|
||||||
issues = IssueSerializer(
|
# Only use serializer when expand else return by values
|
||||||
issue_queryset, many=True, fields=fields if fields else None
|
if self.expand or self.fields:
|
||||||
).data
|
issues = IssueSerializer(
|
||||||
|
issue_queryset,
|
||||||
|
many=True,
|
||||||
|
fields=self.fields,
|
||||||
|
expand=self.expand,
|
||||||
|
).data
|
||||||
|
else:
|
||||||
|
issues = issue_queryset.values(
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"state_id",
|
||||||
|
"sort_order",
|
||||||
|
"completed_at",
|
||||||
|
"estimate_point",
|
||||||
|
"priority",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"sequence_id",
|
||||||
|
"project_id",
|
||||||
|
"parent_id",
|
||||||
|
"cycle_id",
|
||||||
|
"module_ids",
|
||||||
|
"label_ids",
|
||||||
|
"assignee_ids",
|
||||||
|
"sub_issues_count",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"created_by",
|
||||||
|
"updated_by",
|
||||||
|
"attachment_count",
|
||||||
|
"link_count",
|
||||||
|
"is_draft",
|
||||||
|
"archived_at",
|
||||||
|
)
|
||||||
return Response(issues, status=status.HTTP_200_OK)
|
return Response(issues, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def create(self, request, slug, project_id):
|
def create(self, request, slug, project_id):
|
||||||
@ -2057,9 +2302,6 @@ class IssueDraftViewSet(BaseViewSet):
|
|||||||
issue = Issue.objects.get(
|
issue = Issue.objects.get(
|
||||||
workspace__slug=slug, project_id=project_id, pk=pk
|
workspace__slug=slug, project_id=project_id, pk=pk
|
||||||
)
|
)
|
||||||
current_instance = json.dumps(
|
|
||||||
IssueSerializer(issue).data, cls=DjangoJSONEncoder
|
|
||||||
)
|
|
||||||
issue.delete()
|
issue.delete()
|
||||||
issue_activity.delay(
|
issue_activity.delay(
|
||||||
type="issue_draft.activity.deleted",
|
type="issue_draft.activity.deleted",
|
||||||
@ -2067,7 +2309,7 @@ class IssueDraftViewSet(BaseViewSet):
|
|||||||
actor_id=str(request.user.id),
|
actor_id=str(request.user.id),
|
||||||
issue_id=str(pk),
|
issue_id=str(pk),
|
||||||
project_id=str(project_id),
|
project_id=str(project_id),
|
||||||
current_instance=current_instance,
|
current_instance={},
|
||||||
epoch=int(timezone.now().timestamp()),
|
epoch=int(timezone.now().timestamp()),
|
||||||
notification=True,
|
notification=True,
|
||||||
origin=request.META.get("HTTP_ORIGIN"),
|
origin=request.META.get("HTTP_ORIGIN"),
|
||||||
|
@ -449,8 +449,7 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
issue_module__module_id=self.kwargs.get("module_id"),
|
issue_module__module_id=self.kwargs.get("module_id"),
|
||||||
)
|
)
|
||||||
.select_related("workspace", "project", "state", "parent")
|
.select_related("workspace", "project", "state", "parent")
|
||||||
.prefetch_related("labels", "assignees")
|
.prefetch_related("assignees", "labels", "issue_module__module")
|
||||||
.prefetch_related("issue_module__module")
|
|
||||||
.annotate(cycle_id=F("issue_cycle__cycle_id"))
|
.annotate(cycle_id=F("issue_cycle__cycle_id"))
|
||||||
.annotate(
|
.annotate(
|
||||||
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
||||||
@ -474,6 +473,32 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
.annotate(count=Func(F("id"), function="Count"))
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
.values("count")
|
.values("count")
|
||||||
)
|
)
|
||||||
|
.annotate(
|
||||||
|
label_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"labels__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(labels__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
assignee_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"assignees__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(assignees__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
module_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"issue_module__module_id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(issue_module__module_id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
)
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
@method_decorator(gzip_page)
|
@method_decorator(gzip_page)
|
||||||
@ -485,10 +510,39 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
]
|
]
|
||||||
filters = issue_filters(request.query_params, "GET")
|
filters = issue_filters(request.query_params, "GET")
|
||||||
issue_queryset = self.get_queryset().filter(**filters)
|
issue_queryset = self.get_queryset().filter(**filters)
|
||||||
serializer = IssueSerializer(
|
if self.fields or self.expand:
|
||||||
issue_queryset, many=True, fields=fields if fields else None
|
issues = IssueSerializer(
|
||||||
)
|
issue_queryset, many=True, fields=fields if fields else None
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
).data
|
||||||
|
else:
|
||||||
|
issues = issue_queryset.values(
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"state_id",
|
||||||
|
"sort_order",
|
||||||
|
"completed_at",
|
||||||
|
"estimate_point",
|
||||||
|
"priority",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"sequence_id",
|
||||||
|
"project_id",
|
||||||
|
"parent_id",
|
||||||
|
"cycle_id",
|
||||||
|
"module_ids",
|
||||||
|
"label_ids",
|
||||||
|
"assignee_ids",
|
||||||
|
"sub_issues_count",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"created_by",
|
||||||
|
"updated_by",
|
||||||
|
"attachment_count",
|
||||||
|
"link_count",
|
||||||
|
"is_draft",
|
||||||
|
"archived_at",
|
||||||
|
)
|
||||||
|
return Response(issues, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
# create multiple issues inside a module
|
# create multiple issues inside a module
|
||||||
def create_module_issues(self, request, slug, project_id, module_id):
|
def create_module_issues(self, request, slug, project_id, module_id):
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# Django imports
|
# Django imports
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
Prefetch,
|
Q,
|
||||||
OuterRef,
|
OuterRef,
|
||||||
Func,
|
Func,
|
||||||
F,
|
F,
|
||||||
@ -13,16 +13,21 @@ from django.db.models import (
|
|||||||
)
|
)
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.decorators.gzip import gzip_page
|
from django.views.decorators.gzip import gzip_page
|
||||||
from django.db.models import Prefetch, OuterRef, Exists
|
from django.contrib.postgres.aggregates import ArrayAgg
|
||||||
|
from django.contrib.postgres.fields import ArrayField
|
||||||
|
from django.db.models import Value, UUIDField
|
||||||
|
from django.db.models.functions import Coalesce
|
||||||
|
from django.contrib.postgres.aggregates import ArrayAgg
|
||||||
|
from django.contrib.postgres.fields import ArrayField
|
||||||
|
from django.db.models import Value, UUIDField
|
||||||
|
|
||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
|
||||||
# Module imports
|
# Module imports
|
||||||
from . import BaseViewSet, BaseAPIView
|
from . import BaseViewSet
|
||||||
from plane.app.serializers import (
|
from plane.app.serializers import (
|
||||||
GlobalViewSerializer,
|
|
||||||
IssueViewSerializer,
|
IssueViewSerializer,
|
||||||
IssueSerializer,
|
IssueSerializer,
|
||||||
IssueViewFavoriteSerializer,
|
IssueViewFavoriteSerializer,
|
||||||
@ -30,22 +35,16 @@ from plane.app.serializers import (
|
|||||||
from plane.app.permissions import (
|
from plane.app.permissions import (
|
||||||
WorkspaceEntityPermission,
|
WorkspaceEntityPermission,
|
||||||
ProjectEntityPermission,
|
ProjectEntityPermission,
|
||||||
WorkspaceViewerPermission,
|
|
||||||
ProjectLitePermission,
|
|
||||||
)
|
)
|
||||||
from plane.db.models import (
|
from plane.db.models import (
|
||||||
Workspace,
|
Workspace,
|
||||||
GlobalView,
|
|
||||||
IssueView,
|
IssueView,
|
||||||
Issue,
|
Issue,
|
||||||
IssueViewFavorite,
|
IssueViewFavorite,
|
||||||
IssueReaction,
|
|
||||||
IssueLink,
|
IssueLink,
|
||||||
IssueAttachment,
|
IssueAttachment,
|
||||||
IssueSubscriber,
|
|
||||||
)
|
)
|
||||||
from plane.utils.issue_filters import issue_filters
|
from plane.utils.issue_filters import issue_filters
|
||||||
from plane.utils.grouper import group_results
|
|
||||||
|
|
||||||
|
|
||||||
class GlobalViewViewSet(BaseViewSet):
|
class GlobalViewViewSet(BaseViewSet):
|
||||||
@ -89,11 +88,54 @@ class GlobalViewIssuesViewSet(BaseViewSet):
|
|||||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||||
.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")
|
||||||
.prefetch_related(
|
.annotate(cycle_id=F("issue_cycle__cycle_id"))
|
||||||
Prefetch(
|
.annotate(
|
||||||
"issue_reactions",
|
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
||||||
queryset=IssueReaction.objects.select_related("actor"),
|
.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")
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
sub_issues_count=Issue.issue_objects.filter(
|
||||||
|
parent=OuterRef("id")
|
||||||
|
)
|
||||||
|
.order_by()
|
||||||
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
|
.values("count")
|
||||||
|
)
|
||||||
|
.annotate(
|
||||||
|
label_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"labels__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(labels__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
assignee_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"assignees__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(assignees__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
module_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"issue_module__module_id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(issue_module__module_id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -123,28 +165,6 @@ class GlobalViewIssuesViewSet(BaseViewSet):
|
|||||||
.filter(**filters)
|
.filter(**filters)
|
||||||
.filter(project__project_projectmember__member=self.request.user)
|
.filter(project__project_projectmember__member=self.request.user)
|
||||||
.annotate(cycle_id=F("issue_cycle__cycle_id"))
|
.annotate(cycle_id=F("issue_cycle__cycle_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")
|
|
||||||
)
|
|
||||||
.annotate(
|
|
||||||
sub_issues_count=Issue.issue_objects.filter(
|
|
||||||
parent=OuterRef("id")
|
|
||||||
)
|
|
||||||
.order_by()
|
|
||||||
.annotate(count=Func(F("id"), function="Count"))
|
|
||||||
.values("count")
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Priority Ordering
|
# Priority Ordering
|
||||||
@ -207,10 +227,39 @@ class GlobalViewIssuesViewSet(BaseViewSet):
|
|||||||
else:
|
else:
|
||||||
issue_queryset = issue_queryset.order_by(order_by_param)
|
issue_queryset = issue_queryset.order_by(order_by_param)
|
||||||
|
|
||||||
serializer = IssueSerializer(
|
if self.fields:
|
||||||
issue_queryset, many=True, fields=fields if fields else None
|
issues = IssueSerializer(
|
||||||
)
|
issue_queryset, many=True, fields=self.fields
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
).data
|
||||||
|
else:
|
||||||
|
issues = issue_queryset.values(
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"state_id",
|
||||||
|
"sort_order",
|
||||||
|
"completed_at",
|
||||||
|
"estimate_point",
|
||||||
|
"priority",
|
||||||
|
"start_date",
|
||||||
|
"target_date",
|
||||||
|
"sequence_id",
|
||||||
|
"project_id",
|
||||||
|
"parent_id",
|
||||||
|
"cycle_id",
|
||||||
|
"module_ids",
|
||||||
|
"label_ids",
|
||||||
|
"assignee_ids",
|
||||||
|
"sub_issues_count",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"created_by",
|
||||||
|
"updated_by",
|
||||||
|
"attachment_count",
|
||||||
|
"link_count",
|
||||||
|
"is_draft",
|
||||||
|
"archived_at",
|
||||||
|
)
|
||||||
|
return Response(issues, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class IssueViewViewSet(BaseViewSet):
|
class IssueViewViewSet(BaseViewSet):
|
||||||
|
@ -26,6 +26,10 @@ from django.db.models import (
|
|||||||
)
|
)
|
||||||
from django.db.models.functions import ExtractWeek, Cast, ExtractDay
|
from django.db.models.functions import ExtractWeek, Cast, ExtractDay
|
||||||
from django.db.models.fields import DateField
|
from django.db.models.fields import DateField
|
||||||
|
from django.contrib.postgres.aggregates import ArrayAgg
|
||||||
|
from django.contrib.postgres.fields import ArrayField
|
||||||
|
from django.db.models import Value, UUIDField
|
||||||
|
from django.db.models.functions import Coalesce
|
||||||
|
|
||||||
# Third party modules
|
# Third party modules
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@ -1380,6 +1384,32 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
|
|||||||
.annotate(count=Func(F("id"), function="Count"))
|
.annotate(count=Func(F("id"), function="Count"))
|
||||||
.values("count")
|
.values("count")
|
||||||
)
|
)
|
||||||
|
.annotate(
|
||||||
|
label_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"labels__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(labels__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
assignee_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"assignees__id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(assignees__id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
module_ids=Coalesce(
|
||||||
|
ArrayAgg(
|
||||||
|
"issue_module__module_id",
|
||||||
|
distinct=True,
|
||||||
|
filter=~Q(issue_module__module_id__isnull=True),
|
||||||
|
),
|
||||||
|
Value([], output_field=ArrayField(UUIDField())),
|
||||||
|
),
|
||||||
|
)
|
||||||
.order_by("created_at")
|
.order_by("created_at")
|
||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
|
@ -48,6 +48,13 @@ export const IssueSubscription: FC<TIssueSubscription> = observer((props) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (!subscription)
|
||||||
|
return (
|
||||||
|
<Loader>
|
||||||
|
<Loader.Item width="92px" height="27px" />
|
||||||
|
</Loader>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{subscription ? (
|
{subscription ? (
|
||||||
|
Loading…
Reference in New Issue
Block a user