From 528edc2b0eb2b9069c25800f19e71cdc841f0103 Mon Sep 17 00:00:00 2001 From: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Date: Mon, 8 Jul 2024 15:20:39 +0530 Subject: [PATCH] [WEB-1854] chore: active cycle estimates (#573) * chore: estimate points float field * chore: active cycle estimate point --- apiserver/plane/app/serializers/__init__.py | 2 - .../plane/app/serializers/active_cycle.py | 70 --- apiserver/plane/app/views/__init__.py | 1 - .../plane/app/views/cycle/active_cycle.py | 478 ------------------ .../plane/ee/serializers/app/active_cycle.py | 11 + .../plane/ee/views/app/cycle/active_cycle.py | 222 ++++++-- 6 files changed, 193 insertions(+), 591 deletions(-) delete mode 100644 apiserver/plane/app/serializers/active_cycle.py delete mode 100644 apiserver/plane/app/views/cycle/active_cycle.py diff --git a/apiserver/plane/app/serializers/__init__.py b/apiserver/plane/app/serializers/__init__.py index ff1e979b03..0e86b08389 100644 --- a/apiserver/plane/app/serializers/__init__.py +++ b/apiserver/plane/app/serializers/__init__.py @@ -39,8 +39,6 @@ from .view import ( IssueViewSerializer, ) -from .active_cycle import ActiveCycleSerializer - from .cycle import ( CycleSerializer, CycleIssueSerializer, diff --git a/apiserver/plane/app/serializers/active_cycle.py b/apiserver/plane/app/serializers/active_cycle.py deleted file mode 100644 index 619863c468..0000000000 --- a/apiserver/plane/app/serializers/active_cycle.py +++ /dev/null @@ -1,70 +0,0 @@ -# Third party imports -from rest_framework import serializers - -# Module imports -from .base import BaseSerializer -from .project import ProjectLiteSerializer -from plane.db.models import ( - Cycle, -) - - -class ActiveCycleSerializer(BaseSerializer): - # favorite - is_favorite = serializers.BooleanField(read_only=True) - total_issues = serializers.IntegerField(read_only=True) - # state group wise distribution - cancelled_issues = serializers.IntegerField(read_only=True) - completed_issues = serializers.IntegerField(read_only=True) - started_issues = serializers.IntegerField(read_only=True) - unstarted_issues = serializers.IntegerField(read_only=True) - backlog_issues = serializers.IntegerField(read_only=True) - - backlog_estimate_points = serializers.IntegerField(read_only=True) - unstarted_estimate_points = serializers.IntegerField(read_only=True) - started_estimate_points = serializers.IntegerField(read_only=True) - cancelled_estimate_points = serializers.IntegerField(read_only=True) - total_estimate_points = serializers.IntegerField(read_only=True) - - # active | draft | upcoming | completed - status = serializers.CharField(read_only=True) - - # project details - project_detail = ProjectLiteSerializer(read_only=True, source="project") - - class Meta: - model = Cycle - fields = [ - # necessary fields - "id", - "workspace_id", - "project_id", - # model fields - "name", - "description", - "start_date", - "end_date", - "owned_by_id", - "view_props", - "sort_order", - "external_source", - "external_id", - "progress_snapshot", - # meta fields - "is_favorite", - "total_issues", - "cancelled_issues", - "completed_issues", - "started_issues", - "unstarted_issues", - "backlog_issues", - "status", - "project_detail", - "backlog_estimate_points", - "unstarted_estimate_points", - "started_estimate_points", - "cancelled_estimate_points", - "total_estimate_points", - - ] - read_only_fields = fields diff --git a/apiserver/plane/app/views/__init__.py b/apiserver/plane/app/views/__init__.py index 70c2663a91..70cbaf6732 100644 --- a/apiserver/plane/app/views/__init__.py +++ b/apiserver/plane/app/views/__init__.py @@ -94,7 +94,6 @@ from .cycle.base import ( CycleViewSet, TransferCycleIssueEndpoint, ) -from .cycle.active_cycle import ActiveCycleEndpoint from .cycle.issue import ( CycleIssueViewSet, ) diff --git a/apiserver/plane/app/views/cycle/active_cycle.py b/apiserver/plane/app/views/cycle/active_cycle.py deleted file mode 100644 index f93b96b5c4..0000000000 --- a/apiserver/plane/app/views/cycle/active_cycle.py +++ /dev/null @@ -1,478 +0,0 @@ -# Django imports -from django.db.models import ( - Case, - CharField, - Count, - Exists, - F, - OuterRef, - Prefetch, - Q, - Value, - Sum, - When, - Subquery, - IntegerField, -) -from django.db.models.functions import Cast, Coalesce -from django.utils import timezone - - -# Module imports -from plane.app.permissions import ( - WorkspaceUserPermission, -) -from plane.app.serializers import ( - ActiveCycleSerializer, -) -from plane.db.models import Cycle, CycleFavorite, Issue, Label, User, Project -from plane.utils.analytics_plot import burndown_plot -from plane.app.views.base import BaseAPIView - - -class ActiveCycleEndpoint(BaseAPIView): - permission_classes = [ - WorkspaceUserPermission, - ] - - def get_results_controller(self, results, plot_type, active_cycles=None): - for cycle in results: - estimate_type = Project.objects.filter( - pk=cycle["project_id"], - workspace__slug=self.kwargs.get("slug"), - estimate__isnull=False, - estimate__type="points", - ).exists() - cycle["estimate_distribution"] = {} - if estimate_type: - assignee_distribution = ( - Issue.objects.filter( - issue_cycle__cycle_id=cycle["id"], - project_id=cycle["project_id"], - workspace__slug=self.kwargs.get("slug"), - ) - .annotate(display_name=F("assignees__display_name")) - .annotate(assignee_id=F("assignees__id")) - .annotate(avatar=F("assignees__avatar")) - .values("display_name", "assignee_id", "avatar") - .annotate( - total_estimates=Sum( - Cast("estimate_point__value", IntegerField()) - ) - ) - .annotate( - completed_estimates=Sum( - Cast("estimate_point__value", IntegerField()), - filter=Q( - completed_at__isnull=False, - archived_at__isnull=True, - is_draft=False, - ), - ) - ) - .annotate( - pending_estimates=Sum( - Cast("estimate_point__value", IntegerField()), - filter=Q( - completed_at__isnull=True, - archived_at__isnull=True, - is_draft=False, - ), - ) - ) - .order_by("display_name") - ) - - label_distribution = ( - Issue.objects.filter( - issue_cycle__cycle_id=cycle["id"], - project_id=cycle["project_id"], - workspace__slug=self.kwargs.get("slug"), - ) - .annotate(label_name=F("labels__name")) - .annotate(color=F("labels__color")) - .annotate(label_id=F("labels__id")) - .values("label_name", "color", "label_id") - .annotate( - total_estimates=Sum( - Cast("estimate_point__value", IntegerField()) - ) - ) - .annotate( - completed_estimates=Sum( - Cast("estimate_point__value", IntegerField()), - filter=Q( - completed_at__isnull=False, - archived_at__isnull=True, - is_draft=False, - ), - ) - ) - .annotate( - pending_estimates=Sum( - Cast("estimate_point__value", IntegerField()), - filter=Q( - completed_at__isnull=True, - archived_at__isnull=True, - is_draft=False, - ), - ) - ) - .order_by("label_name") - ) - cycle["estimate_distribution"] = { - "assignees": assignee_distribution, - "labels": label_distribution, - "completion_chart": {}, - } - - if cycle["start_date"] and cycle["end_date"]: - cycle["estimate_distribution"]["completion_chart"] = ( - burndown_plot( - queryset=active_cycles.get(pk=cycle["id"]), - slug=self.kwargs.get("slug"), - project_id=cycle["project_id"], - cycle_id=cycle["id"], - plot_type="points", - ) - ) - - assignee_distribution = ( - Issue.issue_objects.filter( - issue_cycle__cycle_id=cycle["id"], - project_id=cycle["project_id"], - workspace__slug=self.kwargs.get("slug"), - ) - .annotate(display_name=F("assignees__display_name")) - .annotate(assignee_id=F("assignees__id")) - .annotate(avatar=F("assignees__avatar")) - .values("display_name", "assignee_id", "avatar") - .annotate( - total_issues=Count( - "assignee_id", - filter=Q(archived_at__isnull=True, is_draft=False), - ), - ) - .annotate( - completed_issues=Count( - "assignee_id", - filter=Q( - completed_at__isnull=False, - archived_at__isnull=True, - is_draft=False, - ), - ) - ) - .annotate( - pending_issues=Count( - "assignee_id", - filter=Q( - completed_at__isnull=True, - archived_at__isnull=True, - is_draft=False, - ), - ) - ) - .order_by("display_name") - ) - - label_distribution = ( - Issue.issue_objects.filter( - issue_cycle__cycle_id=cycle["id"], - project_id=cycle["project_id"], - workspace__slug=self.kwargs.get("slug"), - ) - .annotate(label_name=F("labels__name")) - .annotate(color=F("labels__color")) - .annotate(label_id=F("labels__id")) - .values("label_name", "color", "label_id") - .annotate( - total_issues=Count( - "label_id", - filter=Q(archived_at__isnull=True, is_draft=False), - ) - ) - .annotate( - completed_issues=Count( - "label_id", - filter=Q( - completed_at__isnull=False, - archived_at__isnull=True, - is_draft=False, - ), - ) - ) - .annotate( - pending_issues=Count( - "label_id", - filter=Q( - completed_at__isnull=True, - archived_at__isnull=True, - is_draft=False, - ), - ) - ) - .order_by("label_name") - ) - cycle["distribution"] = { - "assignees": assignee_distribution, - "labels": label_distribution, - "completion_chart": {}, - } - if cycle["start_date"] and cycle["end_date"]: - cycle["distribution"]["completion_chart"] = burndown_plot( - queryset=active_cycles.get(pk=cycle["id"]), - slug=self.kwargs.get("slug"), - project_id=cycle["project_id"], - cycle_id=cycle["id"], - plot_type="issues", - ) - return results - - def get(self, request, slug): - plot_type = request.GET.get("plot_type", "issues") - subquery = CycleFavorite.objects.filter( - user=self.request.user, - cycle_id=OuterRef("pk"), - project_id=self.kwargs.get("project_id"), - workspace__slug=self.kwargs.get("slug"), - ) - backlog_estimate_point = ( - Issue.issue_objects.filter( - estimate_point__estimate__type="points", - state__group="backlog", - issue_cycle__cycle_id=OuterRef("pk"), - ) - .values("issue_cycle__cycle_id") - .annotate( - backlog_estimate_point=Sum( - Cast("estimate_point__value", IntegerField()) - ) - ) - .values("backlog_estimate_point")[:1] - ) - unstarted_estimate_point = ( - Issue.issue_objects.filter( - estimate_point__estimate__type="points", - state__group="unstarted", - issue_cycle__cycle_id=OuterRef("pk"), - ) - .values("issue_cycle__cycle_id") - .annotate( - unstarted_estimate_point=Sum( - Cast("estimate_point__value", IntegerField()) - ) - ) - .values("unstarted_estimate_point")[:1] - ) - started_estimate_point = ( - Issue.issue_objects.filter( - estimate_point__estimate__type="points", - state__group="started", - issue_cycle__cycle_id=OuterRef("pk"), - ) - .values("issue_cycle__cycle_id") - .annotate( - started_estimate_point=Sum( - Cast("estimate_point__value", IntegerField()) - ) - ) - .values("started_estimate_point")[:1] - ) - cancelled_estimate_point = ( - Issue.issue_objects.filter( - estimate_point__estimate__type="points", - state__group="cancelled", - issue_cycle__cycle_id=OuterRef("pk"), - ) - .values("issue_cycle__cycle_id") - .annotate( - cancelled_estimate_point=Sum( - Cast("estimate_point__value", IntegerField()) - ) - ) - .values("cancelled_estimate_point")[:1] - ) - completed_estimate_point = ( - Issue.issue_objects.filter( - estimate_point__estimate__type="points", - state__group="completed", - issue_cycle__cycle_id=OuterRef("pk"), - ) - .values("issue_cycle__cycle_id") - .annotate( - completed_estimate_points=Sum( - Cast("estimate_point__value", IntegerField()) - ) - ) - .values("completed_estimate_points")[:1] - ) - total_estimate_point = ( - Issue.issue_objects.filter( - estimate_point__estimate__type="points", - issue_cycle__cycle_id=OuterRef("pk"), - ) - .values("issue_cycle__cycle_id") - .annotate( - total_estimate_points=Sum( - Cast("estimate_point__value", IntegerField()) - ) - ) - .values("total_estimate_points")[:1] - ) - active_cycles = ( - Cycle.objects.filter( - workspace__slug=slug, - project__project_projectmember__member=self.request.user, - project__project_projectmember__is_active=True, - start_date__lte=timezone.now(), - end_date__gte=timezone.now(), - ) - .select_related("project") - .select_related("workspace") - .select_related("owned_by") - .annotate(is_favorite=Exists(subquery)) - .annotate( - total_issues=Count( - "issue_cycle", - filter=Q( - issue_cycle__issue__archived_at__isnull=True, - issue_cycle__issue__is_draft=False, - ), - ) - ) - .annotate( - completed_issues=Count( - "issue_cycle__issue__state__group", - filter=Q( - issue_cycle__issue__state__group="completed", - issue_cycle__issue__archived_at__isnull=True, - issue_cycle__issue__is_draft=False, - ), - ) - ) - .annotate( - cancelled_issues=Count( - "issue_cycle__issue__state__group", - filter=Q( - issue_cycle__issue__state__group="cancelled", - issue_cycle__issue__archived_at__isnull=True, - issue_cycle__issue__is_draft=False, - ), - ) - ) - .annotate( - started_issues=Count( - "issue_cycle__issue__state__group", - filter=Q( - issue_cycle__issue__state__group="started", - issue_cycle__issue__archived_at__isnull=True, - issue_cycle__issue__is_draft=False, - ), - ) - ) - .annotate( - unstarted_issues=Count( - "issue_cycle__issue__state__group", - filter=Q( - issue_cycle__issue__state__group="unstarted", - issue_cycle__issue__archived_at__isnull=True, - issue_cycle__issue__is_draft=False, - ), - ) - ) - .annotate( - backlog_issues=Count( - "issue_cycle__issue__state__group", - filter=Q( - issue_cycle__issue__state__group="backlog", - issue_cycle__issue__archived_at__isnull=True, - issue_cycle__issue__is_draft=False, - ), - ) - ) - .annotate( - status=Case( - When( - Q(start_date__lte=timezone.now()) - & Q(end_date__gte=timezone.now()), - then=Value("CURRENT"), - ), - When( - start_date__gt=timezone.now(), then=Value("UPCOMING") - ), - When(end_date__lt=timezone.now(), then=Value("COMPLETED")), - When( - Q(start_date__isnull=True) & Q(end_date__isnull=True), - then=Value("DRAFT"), - ), - default=Value("DRAFT"), - output_field=CharField(), - ) - ) - .annotate( - backlog_estimate_points=Coalesce( - Subquery(backlog_estimate_point), - Value(0, output_field=IntegerField()), - ), - ) - .annotate( - unstarted_estimate_points=Coalesce( - Subquery(unstarted_estimate_point), - Value(0, output_field=IntegerField()), - ), - ) - .annotate( - started_estimate_points=Coalesce( - Subquery(started_estimate_point), - Value(0, output_field=IntegerField()), - ), - ) - .annotate( - cancelled_estimate_points=Coalesce( - Subquery(cancelled_estimate_point), - Value(0, output_field=IntegerField()), - ), - ) - .annotate( - completed_estimate_points=Coalesce( - Subquery(completed_estimate_point), - Value(0, output_field=IntegerField()), - ), - ) - .annotate( - total_estimate_points=Coalesce( - Subquery(total_estimate_point), - Value(0, output_field=IntegerField()), - ), - ) - .prefetch_related( - Prefetch( - "issue_cycle__issue__assignees", - queryset=User.objects.only( - "avatar", "first_name", "id" - ).distinct(), - ) - ) - .prefetch_related( - Prefetch( - "issue_cycle__issue__labels", - queryset=Label.objects.only( - "name", "color", "id" - ).distinct(), - ) - ) - .order_by("-created_at") - ) - - return self.paginate( - request=request, - queryset=active_cycles, - on_results=lambda active_cycles: ActiveCycleSerializer( - active_cycles, many=True - ).data, - controller=lambda results: self.get_results_controller( - results, plot_type, active_cycles - ), - default_per_page=int(request.GET.get("per_page", 3)), - ) diff --git a/apiserver/plane/ee/serializers/app/active_cycle.py b/apiserver/plane/ee/serializers/app/active_cycle.py index f177b23643..3d7c548c50 100644 --- a/apiserver/plane/ee/serializers/app/active_cycle.py +++ b/apiserver/plane/ee/serializers/app/active_cycle.py @@ -20,6 +20,12 @@ class WorkspaceActiveCycleSerializer(BaseSerializer): unstarted_issues = serializers.IntegerField(read_only=True) backlog_issues = serializers.IntegerField(read_only=True) + backlog_estimate_points = serializers.FloatField(read_only=True) + unstarted_estimate_points = serializers.FloatField(read_only=True) + started_estimate_points = serializers.FloatField(read_only=True) + cancelled_estimate_points = serializers.FloatField(read_only=True) + total_estimate_points = serializers.FloatField(read_only=True) + # active | draft | upcoming | completed status = serializers.CharField(read_only=True) @@ -54,5 +60,10 @@ class WorkspaceActiveCycleSerializer(BaseSerializer): "backlog_issues", "status", "project_detail", + "backlog_estimate_points", + "unstarted_estimate_points", + "started_estimate_points", + "cancelled_estimate_points", + "total_estimate_points", ] read_only_fields = fields diff --git a/apiserver/plane/ee/views/app/cycle/active_cycle.py b/apiserver/plane/ee/views/app/cycle/active_cycle.py index 0205267f0d..2a0cbc8440 100644 --- a/apiserver/plane/ee/views/app/cycle/active_cycle.py +++ b/apiserver/plane/ee/views/app/cycle/active_cycle.py @@ -9,18 +9,18 @@ from django.db.models import ( Prefetch, Q, Value, - When, Sum, - IntegerField + When, + Subquery, + UUIDField, + FloatField, ) -from django.db.models.functions import Cast +from django.db.models.functions import Cast, Coalesce from django.utils import timezone - +from django.contrib.postgres.aggregates import ArrayAgg +from django.contrib.postgres.fields import ArrayField # Module imports -from plane.db.models import Cycle, CycleFavorite, Issue, Label, User, Project -from plane.utils.analytics_plot import burndown_plot -# ee imports from plane.ee.views.base import BaseAPIView from plane.ee.permissions import ( WorkspaceUserPermission, @@ -28,6 +28,8 @@ from plane.ee.permissions import ( from plane.ee.serializers import ( WorkspaceActiveCycleSerializer, ) +from plane.db.models import Cycle, UserFavorite, Issue, Label, User, Project +from plane.utils.analytics_plot import burndown_plot class WorkspaceActiveCycleEndpoint(BaseAPIView): @@ -58,12 +60,12 @@ class WorkspaceActiveCycleEndpoint(BaseAPIView): .values("display_name", "assignee_id", "avatar") .annotate( total_estimates=Sum( - Cast("estimate_point__value", IntegerField()) + Cast("estimate_point__value", FloatField()) ) ) .annotate( completed_estimates=Sum( - Cast("estimate_point__value", IntegerField()), + Cast("estimate_point__value", FloatField()), filter=Q( completed_at__isnull=False, archived_at__isnull=True, @@ -73,7 +75,7 @@ class WorkspaceActiveCycleEndpoint(BaseAPIView): ) .annotate( pending_estimates=Sum( - Cast("estimate_point__value", IntegerField()), + Cast("estimate_point__value", FloatField()), filter=Q( completed_at__isnull=True, archived_at__isnull=True, @@ -96,12 +98,12 @@ class WorkspaceActiveCycleEndpoint(BaseAPIView): .values("label_name", "color", "label_id") .annotate( total_estimates=Sum( - Cast("estimate_point__value", IntegerField()) + Cast("estimate_point__value", FloatField()) ) ) .annotate( completed_estimates=Sum( - Cast("estimate_point__value", IntegerField()), + Cast("estimate_point__value", FloatField()), filter=Q( completed_at__isnull=False, archived_at__isnull=True, @@ -111,7 +113,7 @@ class WorkspaceActiveCycleEndpoint(BaseAPIView): ) .annotate( pending_estimates=Sum( - Cast("estimate_point__value", IntegerField()), + Cast("estimate_point__value", FloatField()), filter=Q( completed_at__isnull=True, archived_at__isnull=True, @@ -226,17 +228,103 @@ class WorkspaceActiveCycleEndpoint(BaseAPIView): slug=self.kwargs.get("slug"), project_id=cycle["project_id"], cycle_id=cycle["id"], - plot_type="issues" + plot_type="issues", ) return results def get(self, request, slug): - subquery = CycleFavorite.objects.filter( + + favorite_subquery = UserFavorite.objects.filter( user=self.request.user, - cycle_id=OuterRef("pk"), + entity_identifier=OuterRef("pk"), + entity_type="cycle", project_id=self.kwargs.get("project_id"), workspace__slug=self.kwargs.get("slug"), ) + backlog_estimate_point = ( + Issue.issue_objects.filter( + estimate_point__estimate__type="points", + state__group="backlog", + issue_cycle__cycle_id=OuterRef("pk"), + ) + .values("issue_cycle__cycle_id") + .annotate( + backlog_estimate_point=Sum( + Cast("estimate_point__value", FloatField()) + ) + ) + .values("backlog_estimate_point")[:1] + ) + unstarted_estimate_point = ( + Issue.issue_objects.filter( + estimate_point__estimate__type="points", + state__group="unstarted", + issue_cycle__cycle_id=OuterRef("pk"), + ) + .values("issue_cycle__cycle_id") + .annotate( + unstarted_estimate_point=Sum( + Cast("estimate_point__value", FloatField()) + ) + ) + .values("unstarted_estimate_point")[:1] + ) + started_estimate_point = ( + Issue.issue_objects.filter( + estimate_point__estimate__type="points", + state__group="started", + issue_cycle__cycle_id=OuterRef("pk"), + ) + .values("issue_cycle__cycle_id") + .annotate( + started_estimate_point=Sum( + Cast("estimate_point__value", FloatField()) + ) + ) + .values("started_estimate_point")[:1] + ) + cancelled_estimate_point = ( + Issue.issue_objects.filter( + estimate_point__estimate__type="points", + state__group="cancelled", + issue_cycle__cycle_id=OuterRef("pk"), + ) + .values("issue_cycle__cycle_id") + .annotate( + cancelled_estimate_point=Sum( + Cast("estimate_point__value", FloatField()) + ) + ) + .values("cancelled_estimate_point")[:1] + ) + completed_estimate_point = ( + Issue.issue_objects.filter( + estimate_point__estimate__type="points", + state__group="completed", + issue_cycle__cycle_id=OuterRef("pk"), + ) + .values("issue_cycle__cycle_id") + .annotate( + completed_estimate_points=Sum( + Cast("estimate_point__value", FloatField()) + ) + ) + .values("completed_estimate_points")[:1] + ) + total_estimate_point = ( + Issue.issue_objects.filter( + estimate_point__estimate__type="points", + issue_cycle__cycle_id=OuterRef("pk"), + ) + .values("issue_cycle__cycle_id") + .annotate( + total_estimate_points=Sum( + Cast("estimate_point__value", FloatField()) + ) + ) + .values("total_estimate_points")[:1] + ) + active_cycles = ( Cycle.objects.filter( workspace__slug=slug, @@ -245,13 +333,29 @@ class WorkspaceActiveCycleEndpoint(BaseAPIView): start_date__lte=timezone.now(), end_date__gte=timezone.now(), ) - .select_related("project") - .select_related("workspace") - .select_related("owned_by") - .annotate(is_favorite=Exists(subquery)) + .filter(project__archived_at__isnull=True) + .select_related("project", "workspace", "owned_by") + .prefetch_related( + Prefetch( + "issue_cycle__issue__assignees", + queryset=User.objects.only( + "avatar", "first_name", "id" + ).distinct(), + ) + ) + .prefetch_related( + Prefetch( + "issue_cycle__issue__labels", + queryset=Label.objects.only( + "name", "color", "id" + ).distinct(), + ) + ) + .annotate(is_favorite=Exists(favorite_subquery)) .annotate( total_issues=Count( - "issue_cycle", + "issue_cycle__issue__id", + distinct=True, filter=Q( issue_cycle__issue__archived_at__isnull=True, issue_cycle__issue__is_draft=False, @@ -260,7 +364,8 @@ class WorkspaceActiveCycleEndpoint(BaseAPIView): ) .annotate( completed_issues=Count( - "issue_cycle__issue__state__group", + "issue_cycle__issue__id", + distinct=True, filter=Q( issue_cycle__issue__state__group="completed", issue_cycle__issue__archived_at__isnull=True, @@ -270,7 +375,8 @@ class WorkspaceActiveCycleEndpoint(BaseAPIView): ) .annotate( cancelled_issues=Count( - "issue_cycle__issue__state__group", + "issue_cycle__issue__id", + distinct=True, filter=Q( issue_cycle__issue__state__group="cancelled", issue_cycle__issue__archived_at__isnull=True, @@ -280,7 +386,8 @@ class WorkspaceActiveCycleEndpoint(BaseAPIView): ) .annotate( started_issues=Count( - "issue_cycle__issue__state__group", + "issue_cycle__issue__id", + distinct=True, filter=Q( issue_cycle__issue__state__group="started", issue_cycle__issue__archived_at__isnull=True, @@ -290,7 +397,8 @@ class WorkspaceActiveCycleEndpoint(BaseAPIView): ) .annotate( unstarted_issues=Count( - "issue_cycle__issue__state__group", + "issue_cycle__issue__id", + distinct=True, filter=Q( issue_cycle__issue__state__group="unstarted", issue_cycle__issue__archived_at__isnull=True, @@ -300,7 +408,8 @@ class WorkspaceActiveCycleEndpoint(BaseAPIView): ) .annotate( backlog_issues=Count( - "issue_cycle__issue__state__group", + "issue_cycle__issue__id", + distinct=True, filter=Q( issue_cycle__issue__state__group="backlog", issue_cycle__issue__archived_at__isnull=True, @@ -327,23 +436,56 @@ class WorkspaceActiveCycleEndpoint(BaseAPIView): output_field=CharField(), ) ) - .prefetch_related( - Prefetch( - "issue_cycle__issue__assignees", - queryset=User.objects.only( - "avatar", "first_name", "id" - ).distinct(), + .annotate( + assignee_ids=Coalesce( + ArrayAgg( + "issue_cycle__issue__assignees__id", + distinct=True, + filter=~Q( + issue_cycle__issue__assignees__id__isnull=True + ), + ), + Value([], output_field=ArrayField(UUIDField())), ) ) - .prefetch_related( - Prefetch( - "issue_cycle__issue__labels", - queryset=Label.objects.only( - "name", "color", "id" - ).distinct(), - ) + .annotate( + backlog_estimate_points=Coalesce( + Subquery(backlog_estimate_point), + Value(0, output_field=FloatField()), + ), ) - .order_by("-created_at") + .annotate( + unstarted_estimate_points=Coalesce( + Subquery(unstarted_estimate_point), + Value(0, output_field=FloatField()), + ), + ) + .annotate( + started_estimate_points=Coalesce( + Subquery(started_estimate_point), + Value(0, output_field=FloatField()), + ), + ) + .annotate( + cancelled_estimate_points=Coalesce( + Subquery(cancelled_estimate_point), + Value(0, output_field=FloatField()), + ), + ) + .annotate( + completed_estimate_points=Coalesce( + Subquery(completed_estimate_point), + Value(0, output_field=FloatField()), + ), + ) + .annotate( + total_estimate_points=Coalesce( + Subquery(total_estimate_point), + Value(0, output_field=FloatField()), + ), + ) + .order_by("-is_favorite", "name") + .distinct() ) return self.paginate(