diff --git a/apiserver/plane/space/utils/grouper.py b/apiserver/plane/space/utils/grouper.py index 274058842..b334999de 100644 --- a/apiserver/plane/space/utils/grouper.py +++ b/apiserver/plane/space/utils/grouper.py @@ -3,6 +3,9 @@ from django.contrib.postgres.aggregates import ArrayAgg from django.contrib.postgres.fields import ArrayField from django.db.models import Q, UUIDField, Value, F, Case, When, JSONField, CharField from django.db.models.functions import Coalesce, JSONObject, Concat +from django.db.models import QuerySet + +from typing import List, Optional, Dict, Any, Union # Module imports from plane.db.models import ( @@ -17,13 +20,25 @@ from plane.db.models import ( ) -def issue_queryset_grouper(queryset, group_by, sub_group_by): +def issue_queryset_grouper( + queryset: QuerySet[Issue], group_by: Optional[str], sub_group_by: Optional[str] +) -> QuerySet[Issue]: FIELD_MAPPER = { "label_ids": "labels__id", "assignee_ids": "assignees__id", "module_ids": "issue_module__module_id", } + GROUP_FILTER_MAPPER = { + "assignees__id": Q(issue_assignee__deleted_at__isnull=True), + "labels__id": Q(label_issue__deleted_at__isnull=True), + "issue_module__module_id": Q(issue_module__deleted_at__isnull=True), + } + + for group_key in [group_by, sub_group_by]: + if group_key in GROUP_FILTER_MAPPER: + queryset = queryset.filter(GROUP_FILTER_MAPPER[group_key]) + annotations_map = { "assignee_ids": ( "assignees__id", @@ -50,7 +65,9 @@ def issue_queryset_grouper(queryset, group_by, sub_group_by): return queryset.annotate(**default_annotations) -def issue_on_results(issues, group_by, sub_group_by): +def issue_on_results( + issues: QuerySet[Issue], group_by: Optional[str], sub_group_by: Optional[str] +) -> List[Dict[str, Any]]: FIELD_MAPPER = { "labels__id": "label_ids", "assignees__id": "assignee_ids", @@ -160,7 +177,12 @@ def issue_on_results(issues, group_by, sub_group_by): return issues -def issue_group_values(field, slug, project_id=None, filters=dict): +def issue_group_values( + field: str, + slug: str, + project_id: Optional[str] = None, + filters: Dict[str, Any] = {}, +) -> List[Union[str, Any]]: if field == "state_id": queryset = State.objects.filter( is_triage=False, workspace__slug=slug diff --git a/apiserver/plane/utils/grouper.py b/apiserver/plane/utils/grouper.py index e139cdcc5..89e154a7f 100644 --- a/apiserver/plane/utils/grouper.py +++ b/apiserver/plane/utils/grouper.py @@ -1,7 +1,7 @@ # Django imports from django.contrib.postgres.aggregates import ArrayAgg from django.contrib.postgres.fields import ArrayField -from django.db.models import Q, UUIDField, Value +from django.db.models import Q, UUIDField, Value, QuerySet from django.db.models.functions import Coalesce # Module imports @@ -15,16 +15,31 @@ from plane.db.models import ( State, WorkspaceMember, ) +from typing import Optional, Dict, Tuple, Any, Union, List -def issue_queryset_grouper(queryset, group_by, sub_group_by): - FIELD_MAPPER = { +def issue_queryset_grouper( + queryset: QuerySet[Issue], + group_by: Optional[str], + sub_group_by: Optional[str], +) -> QuerySet[Issue]: + FIELD_MAPPER: Dict[str, str] = { "label_ids": "labels__id", "assignee_ids": "assignees__id", "module_ids": "issue_module__module_id", } - annotations_map = { + GROUP_FILTER_MAPPER: Dict[str, Q] = { + "assignees__id": Q(issue_assignee__deleted_at__isnull=True), + "labels__id": Q(label_issue__deleted_at__isnull=True), + "issue_module__module_id": Q(issue_module__deleted_at__isnull=True), + } + + for group_key in [group_by, sub_group_by]: + if group_key in GROUP_FILTER_MAPPER: + queryset = queryset.filter(GROUP_FILTER_MAPPER[group_key]) + + annotations_map: Dict[str, Tuple[str, Q]] = { "assignee_ids": ( "assignees__id", ~Q(assignees__id__isnull=True) & Q(issue_assignee__deleted_at__isnull=True), @@ -42,7 +57,8 @@ def issue_queryset_grouper(queryset, group_by, sub_group_by): ), ), } - default_annotations = { + + default_annotations: Dict[str, Any] = { key: Coalesce( ArrayAgg(field, distinct=True, filter=condition), Value([], output_field=ArrayField(UUIDField())), @@ -54,16 +70,20 @@ def issue_queryset_grouper(queryset, group_by, sub_group_by): return queryset.annotate(**default_annotations) -def issue_on_results(issues, group_by, sub_group_by): - FIELD_MAPPER = { +def issue_on_results( + issues: QuerySet[Issue], + group_by: Optional[str], + sub_group_by: Optional[str], +) -> List[Dict[str, Any]]: + FIELD_MAPPER: Dict[str, str] = { "labels__id": "label_ids", "assignees__id": "assignee_ids", "issue_module__module_id": "module_ids", } - original_list = ["assignee_ids", "label_ids", "module_ids"] + original_list: List[str] = ["assignee_ids", "label_ids", "module_ids"] - required_fields = [ + required_fields: List[str] = [ "id", "name", "state_id", @@ -98,62 +118,72 @@ def issue_on_results(issues, group_by, sub_group_by): original_list.append(sub_group_by) required_fields.extend(original_list) - return issues.values(*required_fields) + return list(issues.values(*required_fields)) -def issue_group_values(field, slug, project_id=None, filters=dict): +def issue_group_values( + field: str, + slug: str, + project_id: Optional[str] = None, + filters: Dict[str, Any] = {}, +) -> List[Union[str, Any]]: if field == "state_id": queryset = State.objects.filter( is_triage=False, workspace__slug=slug ).values_list("id", flat=True) if project_id: return list(queryset.filter(project_id=project_id)) - else: - return list(queryset) + return list(queryset) + if field == "labels__id": queryset = Label.objects.filter(workspace__slug=slug).values_list( "id", flat=True ) if project_id: return list(queryset.filter(project_id=project_id)) + ["None"] - else: - return list(queryset) + ["None"] + return list(queryset) + ["None"] + if field == "assignees__id": if project_id: - return ProjectMember.objects.filter( - workspace__slug=slug, project_id=project_id, is_active=True - ).values_list("member_id", flat=True) - else: return list( - WorkspaceMember.objects.filter( - workspace__slug=slug, is_active=True + ProjectMember.objects.filter( + workspace__slug=slug, project_id=project_id, is_active=True ).values_list("member_id", flat=True) ) + return list( + WorkspaceMember.objects.filter( + workspace__slug=slug, is_active=True + ).values_list("member_id", flat=True) + ) + if field == "issue_module__module_id": queryset = Module.objects.filter(workspace__slug=slug).values_list( "id", flat=True ) if project_id: return list(queryset.filter(project_id=project_id)) + ["None"] - else: - return list(queryset) + ["None"] + return list(queryset) + ["None"] + if field == "cycle_id": queryset = Cycle.objects.filter(workspace__slug=slug).values_list( "id", flat=True ) if project_id: return list(queryset.filter(project_id=project_id)) + ["None"] - else: - return list(queryset) + ["None"] + return list(queryset) + ["None"] + if field == "project_id": queryset = Project.objects.filter(workspace__slug=slug).values_list( "id", flat=True ) return list(queryset) + if field == "priority": return ["low", "medium", "high", "urgent", "none"] + if field == "state__group": return ["backlog", "unstarted", "started", "completed", "cancelled"] + if field == "target_date": queryset = ( Issue.issue_objects.filter(workspace__slug=slug) @@ -163,8 +193,8 @@ def issue_group_values(field, slug, project_id=None, filters=dict): ) if project_id: return list(queryset.filter(project_id=project_id)) - else: - return list(queryset) + return list(queryset) + if field == "start_date": queryset = ( Issue.issue_objects.filter(workspace__slug=slug) @@ -174,8 +204,7 @@ def issue_group_values(field, slug, project_id=None, filters=dict): ) if project_id: return list(queryset.filter(project_id=project_id)) - else: - return list(queryset) + return list(queryset) if field == "created_by": queryset = ( @@ -186,7 +215,6 @@ def issue_group_values(field, slug, project_id=None, filters=dict): ) if project_id: return list(queryset.filter(project_id=project_id)) - else: - return list(queryset) + return list(queryset) return []