Skip to content

Commit 13db2f8

Browse files
authored
enhance sub-issue query performance with optimized annotations and subqueries (#8889)
1 parent bbf14fb commit 13db2f8

File tree

1 file changed

+98
-67
lines changed

1 file changed

+98
-67
lines changed

apps/api/plane/app/views/issue/sub_issue.py

Lines changed: 98 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
# Django imports
99
from django.utils import timezone
10-
from django.db.models import OuterRef, Func, F, Q, Value, UUIDField, Subquery
10+
from django.db.models import OuterRef, Func, F, Q, Value, UUIDField, Subquery, Count, IntegerField
1111
from django.utils.decorators import method_decorator
1212
from django.views.decorators.gzip import gzip_page
1313
from django.contrib.postgres.aggregates import ArrayAgg
@@ -22,7 +22,7 @@
2222
from .. import BaseAPIView
2323
from plane.app.serializers import IssueSerializer
2424
from plane.app.permissions import ProjectEntityPermission
25-
from plane.db.models import Issue, IssueLink, FileAsset, CycleIssue
25+
from plane.db.models import Issue, IssueLink, FileAsset, CycleIssue, IssueLabel, IssueAssignee, ModuleIssue
2626
from plane.bgtasks.issue_activities_task import issue_activity
2727
from plane.utils.timezone_converter import user_timezone_converter
2828
from collections import defaultdict
@@ -37,70 +37,97 @@ class SubIssuesEndpoint(BaseAPIView):
3737
def get(self, request, slug, project_id, issue_id):
3838
sub_issues = (
3939
Issue.issue_objects.filter(parent_id=issue_id, workspace__slug=slug)
40-
.select_related("workspace", "project", "state", "parent")
41-
.prefetch_related("assignees", "labels", "issue_module__module")
4240
.annotate(
4341
cycle_id=Subquery(
4442
CycleIssue.objects.filter(issue=OuterRef("id"), deleted_at__isnull=True).values("cycle_id")[:1]
4543
)
4644
)
4745
.annotate(
48-
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
49-
.order_by()
50-
.annotate(count=Func(F("id"), function="Count"))
51-
.values("count")
46+
link_count=Coalesce(
47+
Subquery(
48+
IssueLink.objects.filter(issue=OuterRef("id"))
49+
.order_by()
50+
.values("issue")
51+
.annotate(count=Count("id"))
52+
.values("count"),
53+
output_field=IntegerField(),
54+
),
55+
0,
56+
)
5257
)
5358
.annotate(
54-
attachment_count=FileAsset.objects.filter(
55-
issue_id=OuterRef("id"),
56-
entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
59+
attachment_count=Coalesce(
60+
Subquery(
61+
FileAsset.objects.filter(
62+
issue_id=OuterRef("id"),
63+
entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
64+
)
65+
.order_by()
66+
.values("issue_id")
67+
.annotate(count=Count("id"))
68+
.values("count"),
69+
output_field=IntegerField(),
70+
),
71+
0,
5772
)
58-
.order_by()
59-
.annotate(count=Func(F("id"), function="Count"))
60-
.values("count")
6173
)
6274
.annotate(
63-
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
64-
.order_by()
65-
.annotate(count=Func(F("id"), function="Count"))
66-
.values("count")
75+
sub_issues_count=Coalesce(
76+
Subquery(
77+
Issue.issue_objects.filter(parent=OuterRef("id"))
78+
.order_by()
79+
.values("parent")
80+
.annotate(count=Count("id"))
81+
.values("count"),
82+
output_field=IntegerField(),
83+
),
84+
0,
85+
)
6786
)
6887
.annotate(
6988
label_ids=Coalesce(
70-
ArrayAgg(
71-
"labels__id",
72-
distinct=True,
73-
filter=Q(~Q(labels__id__isnull=True) & Q(label_issue__deleted_at__isnull=True)),
89+
Subquery(
90+
IssueLabel.objects.filter(issue_id=OuterRef("id"), deleted_at__isnull=True)
91+
.order_by()
92+
.values("issue_id")
93+
.annotate(arr=ArrayAgg("label_id", distinct=True))
94+
.values("arr"),
95+
output_field=ArrayField(UUIDField()),
7496
),
7597
Value([], output_field=ArrayField(UUIDField())),
7698
),
7799
assignee_ids=Coalesce(
78-
ArrayAgg(
79-
"assignees__id",
80-
distinct=True,
81-
filter=Q(
82-
~Q(assignees__id__isnull=True)
83-
& Q(assignees__member_project__is_active=True)
84-
& Q(issue_assignee__deleted_at__isnull=True)
85-
),
100+
Subquery(
101+
IssueAssignee.objects.filter(
102+
issue_id=OuterRef("id"),
103+
assignee__member_project__is_active=True,
104+
deleted_at__isnull=True,
105+
)
106+
.order_by()
107+
.values("issue_id")
108+
.annotate(arr=ArrayAgg("assignee_id", distinct=True))
109+
.values("arr"),
110+
output_field=ArrayField(UUIDField()),
86111
),
87112
Value([], output_field=ArrayField(UUIDField())),
88113
),
89114
module_ids=Coalesce(
90-
ArrayAgg(
91-
"issue_module__module_id",
92-
distinct=True,
93-
filter=Q(
94-
~Q(issue_module__module_id__isnull=True)
95-
& Q(issue_module__module__archived_at__isnull=True)
96-
& Q(issue_module__deleted_at__isnull=True)
97-
),
115+
Subquery(
116+
ModuleIssue.objects.filter(
117+
issue_id=OuterRef("id"),
118+
module__archived_at__isnull=True,
119+
deleted_at__isnull=True,
120+
)
121+
.order_by()
122+
.values("issue_id")
123+
.annotate(arr=ArrayAgg("module_id", distinct=True))
124+
.values("arr"),
125+
output_field=ArrayField(UUIDField()),
98126
),
99127
Value([], output_field=ArrayField(UUIDField())),
100128
),
101129
)
102130
.annotate(state_group=F("state__group"))
103-
.order_by("-created_at")
104131
)
105132

106133
# Ordering
@@ -110,38 +137,42 @@ def get(self, request, slug, project_id, issue_id):
110137
if order_by_param:
111138
sub_issues, order_by_param = order_issue_queryset(sub_issues, order_by_param)
112139

140+
sub_issues = list(
141+
sub_issues.values(
142+
"id",
143+
"name",
144+
"state_id",
145+
"sort_order",
146+
"completed_at",
147+
"estimate_point",
148+
"priority",
149+
"start_date",
150+
"target_date",
151+
"sequence_id",
152+
"project_id",
153+
"parent_id",
154+
"cycle_id",
155+
"module_ids",
156+
"label_ids",
157+
"assignee_ids",
158+
"sub_issues_count",
159+
"created_at",
160+
"updated_at",
161+
"created_by",
162+
"updated_by",
163+
"attachment_count",
164+
"link_count",
165+
"is_draft",
166+
"archived_at",
167+
"state_group",
168+
)
169+
)
170+
113171
# create's a dict with state group name with their respective issue id's
114172
result = defaultdict(list)
115173
for sub_issue in sub_issues:
116-
result[sub_issue.state_group].append(str(sub_issue.id))
174+
result[sub_issue["state_group"]].append(str(sub_issue["id"]))
117175

118-
sub_issues = sub_issues.values(
119-
"id",
120-
"name",
121-
"state_id",
122-
"sort_order",
123-
"completed_at",
124-
"estimate_point",
125-
"priority",
126-
"start_date",
127-
"target_date",
128-
"sequence_id",
129-
"project_id",
130-
"parent_id",
131-
"cycle_id",
132-
"module_ids",
133-
"label_ids",
134-
"assignee_ids",
135-
"sub_issues_count",
136-
"created_at",
137-
"updated_at",
138-
"created_by",
139-
"updated_by",
140-
"attachment_count",
141-
"link_count",
142-
"is_draft",
143-
"archived_at",
144-
)
145176
datetime_fields = ["created_at", "updated_at"]
146177
sub_issues = user_timezone_converter(sub_issues, datetime_fields, request.user.user_timezone)
147178
# Grouping

0 commit comments

Comments
 (0)