From c4594bff0125a9e624f76996a5a43cf349e886bb Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Wed, 22 Mar 2023 01:36:52 +0530 Subject: [PATCH] feat: cycles and modules issues state group percentages (#484) * dev: state group issue percentage on cycle list * dev: add issue percentage fields for modules and query updates on cycle apis --- apiserver/plane/api/serializers/cycle.py | 8 +- apiserver/plane/api/serializers/module.py | 7 +- apiserver/plane/api/views/cycle.py | 222 +++++++++++++++++++--- apiserver/plane/api/views/module.py | 41 +++- 4 files changed, 245 insertions(+), 33 deletions(-) diff --git a/apiserver/plane/api/serializers/cycle.py b/apiserver/plane/api/serializers/cycle.py index d96a70d8c..fefd7b817 100644 --- a/apiserver/plane/api/serializers/cycle.py +++ b/apiserver/plane/api/serializers/cycle.py @@ -11,7 +11,13 @@ from plane.db.models import Cycle, CycleIssue, CycleFavorite class CycleSerializer(BaseSerializer): owned_by = UserLiteSerializer(read_only=True) is_favorite = serializers.BooleanField(read_only=True) - + total_issues = serializers.IntegerField(read_only=True) + 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) + class Meta: model = Cycle fields = "__all__" diff --git a/apiserver/plane/api/serializers/module.py b/apiserver/plane/api/serializers/module.py index bb317a330..4c26f24cf 100644 --- a/apiserver/plane/api/serializers/module.py +++ b/apiserver/plane/api/serializers/module.py @@ -133,9 +133,14 @@ class ModuleSerializer(BaseSerializer): project_detail = ProjectSerializer(read_only=True, source="project") lead_detail = UserLiteSerializer(read_only=True, source="lead") members_detail = UserLiteSerializer(read_only=True, many=True, source="members") - issue_module = ModuleIssueSerializer(read_only=True, many=True) link_module = ModuleLinkSerializer(read_only=True, many=True) is_favorite = serializers.BooleanField(read_only=True) + total_issues = serializers.IntegerField(read_only=True) + 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) class Meta: model = Module diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index b5f04520b..59fdc5f0b 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -3,7 +3,7 @@ import json # Django imports from django.db import IntegrityError -from django.db.models import OuterRef, Func, F, Q, Exists, OuterRef, Prefetch +from django.db.models import OuterRef, Func, F, Q, Exists, OuterRef, Count, Prefetch from django.core import serializers from django.utils import timezone @@ -64,6 +64,37 @@ class CycleViewSet(BaseViewSet): .select_related("workspace") .select_related("owned_by") .annotate(is_favorite=Exists(subquery)) + .annotate(total_issues=Count("issue_cycle")) + .annotate( + completed_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="completed"), + ) + ) + .annotate( + cancelled_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="cancelled"), + ) + ) + .annotate( + started_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="started"), + ) + ) + .annotate( + unstarted_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="unstarted"), + ) + ) + .annotate( + backlog_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="backlog"), + ) + ) .distinct() ) @@ -336,18 +367,92 @@ class CurrentUpcomingCyclesEndpoint(BaseAPIView): project_id=project_id, workspace__slug=slug, ) - current_cycle = Cycle.objects.filter( - workspace__slug=slug, - project_id=project_id, - start_date__lte=timezone.now(), - end_date__gte=timezone.now(), - ).annotate(is_favorite=Exists(subquery)) + current_cycle = ( + Cycle.objects.filter( + workspace__slug=slug, + project_id=project_id, + 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")) + .annotate( + completed_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="completed"), + ) + ) + .annotate( + cancelled_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="cancelled"), + ) + ) + .annotate( + started_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="started"), + ) + ) + .annotate( + unstarted_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="unstarted"), + ) + ) + .annotate( + backlog_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="backlog"), + ) + ) + ) - upcoming_cycle = Cycle.objects.filter( - workspace__slug=slug, - project_id=project_id, - start_date__gt=timezone.now(), - ).annotate(is_favorite=Exists(subquery)) + upcoming_cycle = ( + Cycle.objects.filter( + workspace__slug=slug, + project_id=project_id, + start_date__gt=timezone.now(), + ) + .select_related("project") + .select_related("workspace") + .select_related("owned_by") + .annotate(is_favorite=Exists(subquery)) + .annotate(total_issues=Count("issue_cycle")) + .annotate( + completed_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="completed"), + ) + ) + .annotate( + cancelled_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="cancelled"), + ) + ) + .annotate( + started_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="started"), + ) + ) + .annotate( + unstarted_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="unstarted"), + ) + ) + .annotate( + backlog_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="backlog"), + ) + ) + ) return Response( { @@ -378,11 +483,48 @@ class CompletedCyclesEndpoint(BaseAPIView): project_id=project_id, workspace__slug=slug, ) - completed_cycles = Cycle.objects.filter( - workspace__slug=slug, - project_id=project_id, - end_date__lt=timezone.now(), - ).annotate(is_favorite=Exists(subquery)) + completed_cycles = ( + Cycle.objects.filter( + workspace__slug=slug, + project_id=project_id, + end_date__lt=timezone.now(), + ) + .select_related("project") + .select_related("workspace") + .select_related("owned_by") + .annotate(is_favorite=Exists(subquery)) + .annotate(total_issues=Count("issue_cycle")) + .annotate( + completed_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="completed"), + ) + ) + .annotate( + cancelled_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="cancelled"), + ) + ) + .annotate( + started_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="started"), + ) + ) + .annotate( + unstarted_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="unstarted"), + ) + ) + .annotate( + backlog_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="backlog"), + ) + ) + ) return Response( { @@ -408,11 +550,47 @@ class DraftCyclesEndpoint(BaseAPIView): def get(self, request, slug, project_id): try: - draft_cycles = Cycle.objects.filter( - workspace__slug=slug, - project_id=project_id, - end_date=None, - start_date=None, + draft_cycles = ( + Cycle.objects.filter( + workspace__slug=slug, + project_id=project_id, + end_date=None, + start_date=None, + ) + .select_related("project") + .select_related("workspace") + .select_related("owned_by") + .annotate(total_issues=Count("issue_cycle")) + .annotate( + completed_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="completed"), + ) + ) + .annotate( + cancelled_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="cancelled"), + ) + ) + .annotate( + started_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="started"), + ) + ) + .annotate( + unstarted_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="unstarted"), + ) + ) + .annotate( + backlog_issues=Count( + "issue_cycle__issue__state__group", + filter=Q(issue_cycle__issue__state__group="backlog"), + ) + ) ) return Response( diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index 116c37a96..6112fcba0 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -3,7 +3,7 @@ import json # Django Imports from django.db import IntegrityError -from django.db.models import Prefetch, F, OuterRef, Func, Exists +from django.db.models import Prefetch, F, OuterRef, Func, Exists, Count, Q from django.core import serializers # Third party imports @@ -65,20 +65,43 @@ class ModuleViewSet(BaseViewSet): .select_related("workspace") .select_related("lead") .prefetch_related("members") - .prefetch_related( - Prefetch( - "issue_module", - queryset=ModuleIssue.objects.select_related( - "module", "issue", "issue__state", "issue__project" - ).prefetch_related("issue__assignees", "issue__labels"), - ) - ) .prefetch_related( Prefetch( "link_module", queryset=ModuleLink.objects.select_related("module", "created_by"), ) ) + .annotate(total_issues=Count("issue_module")) + .annotate( + completed_issues=Count( + "issue_module__issue__state__group", + filter=Q(issue_module__issue__state__group="completed"), + ) + ) + .annotate( + cancelled_issues=Count( + "issue_module__issue__state__group", + filter=Q(issue_module__issue__state__group="cancelled"), + ) + ) + .annotate( + started_issues=Count( + "issue_module__issue__state__group", + filter=Q(issue_module__issue__state__group="started"), + ) + ) + .annotate( + unstarted_issues=Count( + "issue_module__issue__state__group", + filter=Q(issue_module__issue__state__group="unstarted"), + ) + ) + .annotate( + backlog_issues=Count( + "issue_module__issue__state__group", + filter=Q(issue_module__issue__state__group="backlog"), + ) + ) ) def create(self, request, slug, project_id):