[WEB-4951] [WEB-4884] feat: work item filters revamp (#7810)

This commit is contained in:
Prateek Shourya 2025-09-19 18:27:36 +05:30 committed by GitHub
parent e6a7ca4c72
commit 9aef5d4aa9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
160 changed files with 5879 additions and 4881 deletions

View file

@ -1080,6 +1080,9 @@ class CycleUserPropertiesEndpoint(BaseAPIView):
)
cycle_properties.filters = request.data.get("filters", cycle_properties.filters)
cycle_properties.rich_filters = request.data.get(
"rich_filters", cycle_properties.rich_filters
)
cycle_properties.display_filters = request.data.get(
"display_filters", cycle_properties.display_filters
)

View file

@ -1,4 +1,5 @@
# Python imports
import copy
import json
# Django imports
@ -28,11 +29,15 @@ from plane.utils.order_queryset import order_issue_queryset
from plane.utils.paginator import GroupedOffsetPaginator, SubGroupedOffsetPaginator
from plane.app.permissions import allow_permission, ROLE
from plane.utils.host import base_host
from plane.utils.filters import ComplexFilterBackend
from plane.utils.filters import IssueFilterSet
class CycleIssueViewSet(BaseViewSet):
serializer_class = CycleIssueSerializer
model = CycleIssue
filter_backends = (ComplexFilterBackend,)
filterset_class = IssueFilterSet
webhook_event = "cycle_issue"
bulk = True
@ -65,24 +70,9 @@ class CycleIssueViewSet(BaseViewSet):
.distinct()
)
@method_decorator(gzip_page)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
def list(self, request, slug, project_id, cycle_id):
order_by_param = request.GET.get("order_by", "created_at")
filters = issue_filters(request.query_params, "GET")
issue_queryset = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id, issue_cycle__deleted_at__isnull=True
)
.filter(project_id=project_id)
.filter(workspace__slug=slug)
.filter(**filters)
.select_related("workspace", "project", "state", "parent")
.prefetch_related(
"assignees", "labels", "issue_module__module", "issue_cycle__cycle"
)
.filter(**filters)
.annotate(
def apply_annotations(self, issues):
return (
issues.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
@ -110,11 +100,36 @@ class CycleIssueViewSet(BaseViewSet):
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.prefetch_related(
"assignees", "labels", "issue_module__module", "issue_cycle__cycle"
)
)
@method_decorator(gzip_page)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
def list(self, request, slug, project_id, cycle_id):
filters = issue_filters(request.query_params, "GET")
issue_queryset = (
Issue.issue_objects.filter(
issue_cycle__cycle_id=cycle_id, issue_cycle__deleted_at__isnull=True
)
.filter(project_id=project_id)
.filter(workspace__slug=slug)
)
# Apply filtering from filterset
issue_queryset = self.filter_queryset(issue_queryset)
# Apply legacy filters
issue_queryset = issue_queryset.filter(**filters)
# Total count queryset
total_issue_queryset = copy.deepcopy(issue_queryset)
# Applying annotations to the issue queryset
issue_queryset = self.apply_annotations(issue_queryset)
order_by_param = request.GET.get("order_by", "-created_at")
issue_queryset = issue_queryset.filter(**filters)
# Issue queryset
issue_queryset, order_by_param = order_issue_queryset(
issue_queryset=issue_queryset, order_by_param=order_by_param
@ -145,6 +160,7 @@ class CycleIssueViewSet(BaseViewSet):
request=request,
order_by=order_by_param,
queryset=issue_queryset,
total_count_queryset=total_issue_queryset,
on_results=lambda issues: issue_on_results(
group_by=group_by, issues=issues, sub_group_by=sub_group_by
),
@ -179,6 +195,7 @@ class CycleIssueViewSet(BaseViewSet):
request=request,
order_by=order_by_param,
queryset=issue_queryset,
total_count_queryset=total_issue_queryset,
on_results=lambda issues: issue_on_results(
group_by=group_by, issues=issues, sub_group_by=sub_group_by
),
@ -205,6 +222,7 @@ class CycleIssueViewSet(BaseViewSet):
order_by=order_by_param,
request=request,
queryset=issue_queryset,
total_count_queryset=total_issue_queryset,
on_results=lambda issues: issue_on_results(
group_by=group_by, issues=issues, sub_group_by=sub_group_by
),

View file

@ -1,4 +1,5 @@
# Python imports
import copy
import json
# Django imports
@ -41,27 +42,20 @@ from plane.utils.host import base_host
# Module imports
from .. import BaseViewSet, BaseAPIView
from plane.utils.filters import ComplexFilterBackend
from plane.utils.filters import IssueFilterSet
class IssueArchiveViewSet(BaseViewSet):
serializer_class = IssueFlatSerializer
model = Issue
def get_queryset(self):
filter_backends = (ComplexFilterBackend,)
filterset_class = IssueFilterSet
def apply_annotations(self, issues):
return (
Issue.objects.annotate(
sub_issues_count=Issue.objects.filter(parent=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.filter(deleted_at__isnull=True)
.filter(archived_at__isnull=False)
.filter(project_id=self.kwargs.get("project_id"))
.filter(workspace__slug=self.kwargs.get("slug"))
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
issues.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
@ -95,6 +89,15 @@ class IssueArchiveViewSet(BaseViewSet):
.values("count")
)
)
.prefetch_related("assignees", "labels", "issue_module__module")
)
def get_queryset(self):
return (
Issue.objects.filter(Q(type__isnull=True) | Q(type__is_epic=False))
.filter(archived_at__isnull=False)
.filter(project_id=self.kwargs.get("project_id"))
.filter(workspace__slug=self.kwargs.get("slug"))
)
@method_decorator(gzip_page)
@ -105,26 +108,25 @@ class IssueArchiveViewSet(BaseViewSet):
order_by_param = request.GET.get("order_by", "-created_at")
issue_queryset = self.get_queryset().filter(**filters)
total_issue_queryset = Issue.objects.filter(
deleted_at__isnull=True,
archived_at__isnull=False,
project_id=project_id,
workspace__slug=slug,
).filter(**filters)
total_issue_queryset = (
total_issue_queryset
if show_sub_issues == "true"
else total_issue_queryset.filter(parent__isnull=True)
)
issue_queryset = self.get_queryset()
issue_queryset = (
issue_queryset
if show_sub_issues == "true"
else issue_queryset.filter(parent__isnull=True)
)
# Apply filtering from filterset
issue_queryset = self.filter_queryset(issue_queryset)
# Apply legacy filters
issue_queryset = issue_queryset.filter(**filters)
# Total count queryset
total_issue_queryset = copy.deepcopy(issue_queryset)
# Applying annotations to the issue queryset
issue_queryset = self.apply_annotations(issue_queryset)
# Issue queryset
issue_queryset, order_by_param = order_issue_queryset(
issue_queryset=issue_queryset, order_by_param=order_by_param

View file

@ -1,4 +1,5 @@
# Python imports
import copy
import json
# Django imports
@ -6,16 +7,16 @@ from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField
from django.core.serializers.json import DjangoJSONEncoder
from django.db.models import (
Count,
Exists,
F,
Func,
OuterRef,
Prefetch,
Q,
Subquery,
UUIDField,
Value,
Subquery,
Count,
)
from django.db.models.functions import Coalesce
from django.utils import timezone
@ -27,50 +28,55 @@ from rest_framework import status
from rest_framework.response import Response
# Module imports
from plane.app.permissions import allow_permission, ROLE
from plane.app.permissions import ROLE, allow_permission
from plane.app.serializers import (
IssueCreateSerializer,
IssueDetailSerializer,
IssueUserPropertySerializer,
IssueSerializer,
IssueListDetailSerializer,
IssueSerializer,
IssueUserPropertySerializer,
)
from plane.bgtasks.issue_activities_task import issue_activity
from plane.bgtasks.issue_description_version_task import issue_description_version_task
from plane.bgtasks.recent_visited_task import recent_visited_task
from plane.bgtasks.webhook_task import model_activity
from plane.db.models import (
Issue,
FileAsset,
IssueLink,
IssueUserProperty,
IssueReaction,
IssueSubscriber,
Project,
ProjectMember,
CycleIssue,
UserRecentVisit,
ModuleIssue,
IssueRelation,
FileAsset,
IntakeIssue,
Issue,
IssueAssignee,
IssueLabel,
IntakeIssue,
IssueLink,
IssueReaction,
IssueRelation,
IssueSubscriber,
IssueUserProperty,
ModuleIssue,
Project,
ProjectMember,
UserRecentVisit,
)
from plane.utils.filters import ComplexFilterBackend, IssueFilterSet
from plane.utils.global_paginator import paginate
from plane.utils.grouper import (
issue_group_values,
issue_on_results,
issue_queryset_grouper,
)
from plane.utils.host import base_host
from plane.utils.issue_filters import issue_filters
from plane.utils.order_queryset import order_issue_queryset
from plane.utils.paginator import GroupedOffsetPaginator, SubGroupedOffsetPaginator
from .. import BaseAPIView, BaseViewSet
from plane.utils.timezone_converter import user_timezone_converter
from plane.bgtasks.recent_visited_task import recent_visited_task
from plane.utils.global_paginator import paginate
from plane.bgtasks.webhook_task import model_activity
from plane.bgtasks.issue_description_version_task import issue_description_version_task
from plane.utils.host import base_host
from .. import BaseAPIView, BaseViewSet
class IssueListEndpoint(BaseAPIView):
filter_backends = (ComplexFilterBackend,)
filterset_class = IssueFilterSet
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
def get(self, request, slug, project_id):
issue_ids = request.GET.get("issues", False)
@ -82,14 +88,27 @@ class IssueListEndpoint(BaseAPIView):
issue_ids = [issue_id for issue_id in issue_ids.split(",") if issue_id != ""]
queryset = (
Issue.issue_objects.filter(
workspace__slug=slug, project_id=project_id, pk__in=issue_ids
)
.filter(workspace__slug=self.kwargs.get("slug"))
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
# Base queryset with basic filters
queryset = Issue.issue_objects.filter(
workspace__slug=slug, project_id=project_id, pk__in=issue_ids
)
# Apply filtering from filterset
queryset = self.filter_queryset(queryset)
# Apply legacy filters
filters = issue_filters(request.query_params, "GET")
issue_queryset = queryset.filter(**filters)
# Add select_related, prefetch_related if fields or expand is not None
if self.fields or self.expand:
issue_queryset = issue_queryset.select_related(
"workspace", "project", "state", "parent"
).prefetch_related("assignees", "labels", "issue_module__module")
# Add annotations
issue_queryset = (
issue_queryset.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
@ -117,12 +136,10 @@ class IssueListEndpoint(BaseAPIView):
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
).distinct()
filters = issue_filters(request.query_params, "GET")
.distinct()
)
order_by_param = request.GET.get("order_by", "-created_at")
issue_queryset = queryset.filter(**filters)
# Issue queryset
issue_queryset, _ = order_issue_queryset(
issue_queryset=issue_queryset, order_by_param=order_by_param
@ -186,6 +203,12 @@ class IssueListEndpoint(BaseAPIView):
class IssueViewSet(BaseViewSet):
model = Issue
webhook_event = "issue"
search_fields = ["name"]
filter_backends = (ComplexFilterBackend,)
filterset_class = IssueFilterSet
def get_serializer_class(self):
return (
IssueCreateSerializer
@ -193,20 +216,17 @@ class IssueViewSet(BaseViewSet):
else IssueSerializer
)
model = Issue
webhook_event = "issue"
search_fields = ["name"]
filterset_fields = ["state__name", "assignees__id", "workspace__id"]
def get_queryset(self):
return (
Issue.issue_objects.filter(project_id=self.kwargs.get("project_id"))
.filter(workspace__slug=self.kwargs.get("slug"))
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
issues = Issue.issue_objects.filter(
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
).distinct()
return issues
def apply_annotations(self, issues):
issues = (
issues.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
@ -242,6 +262,8 @@ class IssueViewSet(BaseViewSet):
)
)
return issues
@method_decorator(gzip_page)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
def list(self, request, slug, project_id):
@ -250,15 +272,24 @@ class IssueViewSet(BaseViewSet):
extra_filters = {"updated_at__gt": request.GET.get("updated_at__gt")}
project = Project.objects.get(pk=project_id, workspace__slug=slug)
filters = issue_filters(request.query_params, "GET")
query_params = request.query_params.copy()
filters = issue_filters(query_params, "GET")
order_by_param = request.GET.get("order_by", "-created_at")
issue_queryset = self.get_queryset().filter(**filters, **extra_filters)
# Custom ordering for priority and state
issue_queryset = self.get_queryset()
total_issue_queryset = Issue.issue_objects.filter(
project_id=project_id, workspace__slug=slug
).filter(**filters, **extra_filters)
# Apply rich filters
issue_queryset = self.filter_queryset(issue_queryset)
# Apply legacy filters
issue_queryset = issue_queryset.filter(**filters, **extra_filters)
# Keeping a copy of the queryset before applying annotations
filtered_issue_queryset = copy.deepcopy(issue_queryset)
# Applying annotations to the issue queryset
issue_queryset = self.apply_annotations(issue_queryset)
# Issue queryset
issue_queryset, order_by_param = order_issue_queryset(
@ -292,14 +323,16 @@ class IssueViewSet(BaseViewSet):
and not project.guest_view_all_features
):
issue_queryset = issue_queryset.filter(created_by=request.user)
total_issue_queryset = total_issue_queryset.filter(created_by=request.user)
filtered_issue_queryset = filtered_issue_queryset.filter(
created_by=request.user
)
if group_by:
if sub_group_by:
if group_by == sub_group_by:
return Response(
{
"error": "Group by and sub group by cannot have same parameters"
"error": "Group by and sub group by cannot have same parameters" # noqa: E501
},
status=status.HTTP_400_BAD_REQUEST,
)
@ -308,7 +341,7 @@ class IssueViewSet(BaseViewSet):
request=request,
order_by=order_by_param,
queryset=issue_queryset,
total_count_queryset=total_issue_queryset,
total_count_queryset=filtered_issue_queryset,
on_results=lambda issues: issue_on_results(
group_by=group_by, issues=issues, sub_group_by=sub_group_by
),
@ -318,12 +351,14 @@ class IssueViewSet(BaseViewSet):
slug=slug,
project_id=project_id,
filters=filters,
queryset=filtered_issue_queryset,
),
sub_group_by_fields=issue_group_values(
field=sub_group_by,
slug=slug,
project_id=project_id,
filters=filters,
queryset=filtered_issue_queryset,
),
group_by_field_name=group_by,
sub_group_by_field_name=sub_group_by,
@ -342,7 +377,7 @@ class IssueViewSet(BaseViewSet):
request=request,
order_by=order_by_param,
queryset=issue_queryset,
total_count_queryset=total_issue_queryset,
total_count_queryset=filtered_issue_queryset,
on_results=lambda issues: issue_on_results(
group_by=group_by, issues=issues, sub_group_by=sub_group_by
),
@ -352,6 +387,7 @@ class IssueViewSet(BaseViewSet):
slug=slug,
project_id=project_id,
filters=filters,
queryset=filtered_issue_queryset,
),
group_by_field_name=group_by,
count_filter=Q(
@ -368,7 +404,7 @@ class IssueViewSet(BaseViewSet):
order_by=order_by_param,
request=request,
queryset=issue_queryset,
total_count_queryset=total_issue_queryset,
total_count_queryset=filtered_issue_queryset,
on_results=lambda issues: issue_on_results(
group_by=group_by, issues=issues, sub_group_by=sub_group_by
),
@ -402,9 +438,11 @@ class IssueViewSet(BaseViewSet):
notification=True,
origin=base_host(request=request, is_app=True),
)
queryset = self.get_queryset()
queryset = self.apply_annotations(queryset)
issue = (
issue_queryset_grouper(
queryset=self.get_queryset().filter(pk=serializer.data["id"]),
queryset=queryset.filter(pk=serializer.data["id"]),
group_by=None,
sub_group_by=None,
)
@ -609,9 +647,10 @@ class IssueViewSet(BaseViewSet):
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER], creator=True, model=Issue
)
def partial_update(self, request, slug, project_id, pk=None):
queryset = self.get_queryset()
queryset = self.apply_annotations(queryset)
issue = (
self.get_queryset()
.annotate(
queryset.annotate(
label_ids=Coalesce(
ArrayAgg(
"labels__id",
@ -730,6 +769,9 @@ class IssueUserDisplayPropertyEndpoint(BaseAPIView):
user=request.user, project_id=project_id
)
issue_property.rich_filters = request.data.get(
"rich_filters", issue_property.rich_filters
)
issue_property.filters = request.data.get("filters", issue_property.filters)
issue_property.display_filters = request.data.get(
"display_filters", issue_property.display_filters
@ -969,6 +1011,59 @@ class IssuePaginatedViewSet(BaseViewSet):
class IssueDetailEndpoint(BaseAPIView):
filter_backends = (ComplexFilterBackend,)
filterset_class = IssueFilterSet
def apply_annotations(self, issues):
return (
issues.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
attachment_count=FileAsset.objects.filter(
issue_id=OuterRef("id"),
entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
)
.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")
)
.prefetch_related(
Prefetch(
"issue_assignee",
queryset=IssueAssignee.objects.all(),
)
)
.prefetch_related(
Prefetch(
"label_issue",
queryset=IssueLabel.objects.all(),
)
)
.prefetch_related(
Prefetch(
"issue_module",
queryset=ModuleIssue.objects.all(),
)
)
)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
def get(self, request, slug, project_id):
filters = issue_filters(request.query_params, "GET")
@ -1002,56 +1097,9 @@ class IssueDetailEndpoint(BaseAPIView):
.values("id")
)
# Main issue query
issue = (
Issue.issue_objects.filter(workspace__slug=slug, project_id=project_id)
.filter(Exists(permission_subquery))
.prefetch_related(
Prefetch(
"issue_assignee",
queryset=IssueAssignee.objects.all(),
)
)
.prefetch_related(
Prefetch(
"label_issue",
queryset=IssueLabel.objects.all(),
)
)
.prefetch_related(
Prefetch(
"issue_module",
queryset=ModuleIssue.objects.all(),
)
)
.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
).values("cycle_id")[:1]
)
)
.annotate(
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
.order_by()
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.annotate(
attachment_count=FileAsset.objects.filter(
issue_id=OuterRef("id"),
entity_type=FileAsset.EntityTypeContext.ISSUE_ATTACHMENT,
)
.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")
)
)
issue = Issue.issue_objects.filter(
workspace__slug=slug, project_id=project_id
).filter(Exists(permission_subquery))
# Add additional prefetch based on expand parameter
if self.expand:
@ -1070,8 +1118,20 @@ class IssueDetailEndpoint(BaseAPIView):
)
)
# Apply filtering from filterset
issue = self.filter_queryset(issue)
# Apply legacy filters
issue = issue.filter(**filters)
# Total count queryset
total_issue_queryset = copy.deepcopy(issue)
# Applying annotations to the issue queryset
issue = self.apply_annotations(issue)
order_by_param = request.GET.get("order_by", "-created_at")
# Issue queryset
issue, order_by_param = order_issue_queryset(
issue_queryset=issue, order_by_param=order_by_param
@ -1079,7 +1139,8 @@ class IssueDetailEndpoint(BaseAPIView):
return self.paginate(
request=request,
order_by=order_by_param,
queryset=(issue),
queryset=issue,
total_count_queryset=total_issue_queryset,
on_results=lambda issue: IssueListDetailSerializer(
issue, many=True, fields=self.fields, expand=self.expand
).data,

View file

@ -904,6 +904,9 @@ class ModuleUserPropertiesEndpoint(BaseAPIView):
module_properties.filters = request.data.get(
"filters", module_properties.filters
)
module_properties.rich_filters = request.data.get(
"rich_filters", module_properties.rich_filters
)
module_properties.display_filters = request.data.get(
"display_filters", module_properties.display_filters
)

View file

@ -1,4 +1,5 @@
# Python imports
import copy
import json
from django.db.models import F, Func, OuterRef, Q, Subquery
@ -31,8 +32,8 @@ from plane.utils.grouper import (
from plane.utils.issue_filters import issue_filters
from plane.utils.order_queryset import order_issue_queryset
from plane.utils.paginator import GroupedOffsetPaginator, SubGroupedOffsetPaginator
# Module imports
from plane.utils.filters import ComplexFilterBackend
from plane.utils.filters import IssueFilterSet
from .. import BaseViewSet
from plane.utils.host import base_host
@ -42,20 +43,12 @@ class ModuleIssueViewSet(BaseViewSet):
model = ModuleIssue
webhook_event = "module_issue"
bulk = True
filter_backends = (ComplexFilterBackend,)
filterset_class = IssueFilterSet
filterset_fields = ["issue__labels__id", "issue__assignees__id"]
def get_queryset(self):
def apply_annotations(self, issues):
return (
Issue.issue_objects.filter(
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
issue_module__module_id=self.kwargs.get("module_id"),
issue_module__deleted_at__isnull=True,
)
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
issues.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
@ -83,13 +76,37 @@ class ModuleIssueViewSet(BaseViewSet):
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.prefetch_related("assignees", "labels", "issue_module__module")
)
def get_queryset(self):
return (
Issue.issue_objects.filter(
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
issue_module__module_id=self.kwargs.get("module_id"),
issue_module__deleted_at__isnull=True,
)
).distinct()
@method_decorator(gzip_page)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER])
def list(self, request, slug, project_id, module_id):
filters = issue_filters(request.query_params, "GET")
issue_queryset = self.get_queryset().filter(**filters)
issue_queryset = self.get_queryset()
# Apply filtering from filterset
issue_queryset = self.filter_queryset(issue_queryset)
# Apply legacy filters
issue_queryset = issue_queryset.filter(**filters)
# Total count queryset
total_issue_queryset = copy.deepcopy(issue_queryset)
# Apply annotations to the issue queryset
issue_queryset = self.apply_annotations(issue_queryset)
order_by_param = request.GET.get("order_by", "created_at")
# Issue queryset
@ -122,6 +139,7 @@ class ModuleIssueViewSet(BaseViewSet):
request=request,
order_by=order_by_param,
queryset=issue_queryset,
total_count_queryset=total_issue_queryset,
on_results=lambda issues: issue_on_results(
group_by=group_by, issues=issues, sub_group_by=sub_group_by
),
@ -131,12 +149,14 @@ class ModuleIssueViewSet(BaseViewSet):
slug=slug,
project_id=project_id,
filters=filters,
queryset=total_issue_queryset,
),
sub_group_by_fields=issue_group_values(
field=sub_group_by,
slug=slug,
project_id=project_id,
filters=filters,
queryset=total_issue_queryset,
),
group_by_field_name=group_by,
sub_group_by_field_name=sub_group_by,
@ -156,6 +176,7 @@ class ModuleIssueViewSet(BaseViewSet):
request=request,
order_by=order_by_param,
queryset=issue_queryset,
total_count_queryset=total_issue_queryset,
on_results=lambda issues: issue_on_results(
group_by=group_by, issues=issues, sub_group_by=sub_group_by
),
@ -165,6 +186,7 @@ class ModuleIssueViewSet(BaseViewSet):
slug=slug,
project_id=project_id,
filters=filters,
queryset=total_issue_queryset,
),
group_by_field_name=group_by,
count_filter=Q(
@ -182,6 +204,7 @@ class ModuleIssueViewSet(BaseViewSet):
order_by=order_by_param,
request=request,
queryset=issue_queryset,
total_count_queryset=total_issue_queryset,
on_results=lambda issues: issue_on_results(
group_by=group_by, issues=issues, sub_group_by=sub_group_by
),
@ -282,9 +305,11 @@ class ModuleIssueViewSet(BaseViewSet):
project_id=str(project_id),
current_instance=json.dumps(
{
"module_name": module_issue.first().module.name
if (module_issue.first() and module_issue.first().module)
else None
"module_name": (
module_issue.first().module.name
if (module_issue.first() and module_issue.first().module)
else None
)
}
),
epoch=int(timezone.now().timestamp()),

View file

@ -1,3 +1,5 @@
import copy
# Django imports
from django.db.models import (
Exists,
@ -39,6 +41,8 @@ from plane.utils.order_queryset import order_issue_queryset
from plane.bgtasks.recent_visited_task import recent_visited_task
from .. import BaseViewSet
from plane.db.models import UserFavorite
from plane.utils.filters import ComplexFilterBackend
from plane.utils.filters import IssueFilterSet
class WorkspaceViewViewSet(BaseViewSet):
@ -56,7 +60,6 @@ class WorkspaceViewViewSet(BaseViewSet):
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project__isnull=True)
.filter(Q(owned_by=self.request.user) | Q(access=1))
.select_related("workspace")
.order_by(self.request.GET.get("order_by", "-created_at"))
.distinct()
)
@ -145,6 +148,9 @@ class WorkspaceViewViewSet(BaseViewSet):
class WorkspaceViewIssuesViewSet(BaseViewSet):
filter_backends = (ComplexFilterBackend,)
filterset_class = IssueFilterSet
def _get_project_permission_filters(self):
"""
Get common project permission filters for guest users and role-based access control.
@ -167,35 +173,9 @@ class WorkspaceViewIssuesViewSet(BaseViewSet):
project__project_projectmember__is_active=True,
)
def get_queryset(self):
def apply_annotations(self, issues):
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("state")
.prefetch_related(
Prefetch(
"issue_assignee",
queryset=IssueAssignee.objects.all(),
)
)
.prefetch_related(
Prefetch(
"label_issue",
queryset=IssueLabel.objects.all(),
)
)
.prefetch_related(
Prefetch(
"issue_module",
queryset=ModuleIssue.objects.all(),
)
)
.annotate(
issues.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
@ -223,32 +203,57 @@ class WorkspaceViewIssuesViewSet(BaseViewSet):
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.prefetch_related(
Prefetch(
"issue_assignee",
queryset=IssueAssignee.objects.all(),
)
)
.prefetch_related(
Prefetch(
"label_issue",
queryset=IssueLabel.objects.all(),
)
)
.prefetch_related(
Prefetch(
"issue_module",
queryset=ModuleIssue.objects.all(),
)
)
)
def get_queryset(self):
return Issue.issue_objects.filter(workspace__slug=self.kwargs.get("slug"))
@method_decorator(gzip_page)
@allow_permission(
allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE"
)
def list(self, request, slug):
filters = issue_filters(request.query_params, "GET")
issue_queryset = self.get_queryset()
# Apply filtering from filterset
issue_queryset = self.filter_queryset(issue_queryset)
order_by_param = request.GET.get("order_by", "-created_at")
issue_queryset = self.get_queryset().filter(**filters)
# Apply legacy filters
filters = issue_filters(request.query_params, "GET")
issue_queryset = issue_queryset.filter(**filters)
# Get common project permission filters
permission_filters = self._get_project_permission_filters()
# Base query for the counts
total_issue_count = (
Issue.issue_objects.filter(**filters)
.filter(workspace__slug=slug)
.filter(permission_filters)
.only("id")
)
# Apply project permission filters to the issue queryset
issue_queryset = issue_queryset.filter(permission_filters)
# Base query for the counts
total_issue_count_queryset = copy.deepcopy(issue_queryset)
total_issue_count_queryset = total_issue_count_queryset.only("id")
# Apply annotations to the issue queryset
issue_queryset = self.apply_annotations(issue_queryset)
# Issue queryset
issue_queryset, order_by_param = order_issue_queryset(
issue_queryset=issue_queryset, order_by_param=order_by_param
@ -260,7 +265,7 @@ class WorkspaceViewIssuesViewSet(BaseViewSet):
request=request,
queryset=issue_queryset,
on_results=lambda issues: ViewIssueListSerializer(issues, many=True).data,
total_count_queryset=total_issue_count,
total_count_queryset=total_issue_count_queryset,
)

View file

@ -1,4 +1,5 @@
# Python imports
import copy
from datetime import date
from dateutil.relativedelta import relativedelta
@ -56,6 +57,8 @@ from plane.utils.grouper import (
from plane.utils.issue_filters import issue_filters
from plane.utils.order_queryset import order_issue_queryset
from plane.utils.paginator import GroupedOffsetPaginator, SubGroupedOffsetPaginator
from plane.utils.filters import ComplexFilterBackend
from plane.utils.filters import IssueFilterSet
class UserLastProjectWithWorkspaceEndpoint(BaseAPIView):
@ -91,23 +94,12 @@ class UserLastProjectWithWorkspaceEndpoint(BaseAPIView):
class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
permission_classes = [WorkspaceViewerPermission]
def get(self, request, slug, user_id):
filters = issue_filters(request.query_params, "GET")
filter_backends = (ComplexFilterBackend,)
filterset_class = IssueFilterSet
order_by_param = request.GET.get("order_by", "-created_at")
issue_queryset = (
Issue.issue_objects.filter(
Q(assignees__in=[user_id])
| Q(created_by_id=user_id)
| Q(issue_subscribers__subscriber_id=user_id),
workspace__slug=slug,
project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True,
)
.filter(**filters)
.select_related("workspace", "project", "state", "parent")
.prefetch_related("assignees", "labels", "issue_module__module")
.annotate(
def apply_annotations(self, issues):
return (
issues.annotate(
cycle_id=Subquery(
CycleIssue.objects.filter(
issue=OuterRef("id"), deleted_at__isnull=True
@ -135,8 +127,36 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
.annotate(count=Func(F("id"), function="Count"))
.values("count")
)
.order_by("created_at")
).distinct()
.prefetch_related("assignees", "labels", "issue_module__module")
)
def get(self, request, slug, user_id):
filters = issue_filters(request.query_params, "GET")
order_by_param = request.GET.get("order_by", "-created_at")
issue_queryset = Issue.issue_objects.filter(
id__in=Issue.issue_objects.filter(
Q(assignees__in=[user_id])
| Q(created_by_id=user_id)
| Q(issue_subscribers__subscriber_id=user_id),
workspace__slug=slug,
).values_list("id", flat=True),
workspace__slug=slug,
project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True,
)
# Apply filtering from filterset
issue_queryset = self.filter_queryset(issue_queryset)
# Apply legacy filters
issue_queryset = issue_queryset.filter(**filters)
# Total count queryset
total_issue_queryset = copy.deepcopy(issue_queryset)
# Apply annotations to the issue queryset
issue_queryset = self.apply_annotations(issue_queryset)
# Issue queryset
issue_queryset, order_by_param = order_issue_queryset(
@ -157,7 +177,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
if group_by == sub_group_by:
return Response(
{
"error": "Group by and sub group by cannot have same parameters"
"error": "Group by and sub group by cannot have same parameters" # noqa: E501
},
status=status.HTTP_400_BAD_REQUEST,
)
@ -166,15 +186,22 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
request=request,
order_by=order_by_param,
queryset=issue_queryset,
total_count_queryset=total_issue_queryset,
on_results=lambda issues: issue_on_results(
group_by=group_by, issues=issues, sub_group_by=sub_group_by
),
paginator_cls=SubGroupedOffsetPaginator,
group_by_fields=issue_group_values(
field=group_by, slug=slug, filters=filters
field=group_by,
slug=slug,
filters=filters,
queryset=total_issue_queryset,
),
sub_group_by_fields=issue_group_values(
field=sub_group_by, slug=slug, filters=filters
field=sub_group_by,
slug=slug,
filters=filters,
queryset=total_issue_queryset,
),
group_by_field_name=group_by,
sub_group_by_field_name=sub_group_by,
@ -193,12 +220,16 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
request=request,
order_by=order_by_param,
queryset=issue_queryset,
total_count_queryset=total_issue_queryset,
on_results=lambda issues: issue_on_results(
group_by=group_by, issues=issues, sub_group_by=sub_group_by
),
paginator_cls=GroupedOffsetPaginator,
group_by_fields=issue_group_values(
field=group_by, slug=slug, filters=filters
field=group_by,
slug=slug,
filters=filters,
queryset=total_issue_queryset,
),
group_by_field_name=group_by,
count_filter=Q(
@ -215,6 +246,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
order_by=order_by_param,
request=request,
queryset=issue_queryset,
total_count_queryset=total_issue_queryset,
on_results=lambda issues: issue_on_results(
group_by=group_by, issues=issues, sub_group_by=sub_group_by
),
@ -232,6 +264,9 @@ class WorkspaceUserPropertiesEndpoint(BaseAPIView):
workspace_properties.filters = request.data.get(
"filters", workspace_properties.filters
)
workspace_properties.rich_filters = request.data.get(
"rich_filters", workspace_properties.rich_filters
)
workspace_properties.display_filters = request.data.get(
"display_filters", workspace_properties.display_filters
)