# Django imports from django.db.models import ( Q, OuterRef, Func, F, Case, Value, CharField, When, Exists, Max, ) from django.utils.decorators import method_decorator from django.views.decorators.gzip import gzip_page from django.contrib.postgres.aggregates import ArrayAgg from django.contrib.postgres.fields import ArrayField from django.db.models import UUIDField from django.db.models.functions import Coalesce # Third party imports from rest_framework.response import Response from rest_framework import status # Module imports from .. import BaseViewSet from plane.app.serializers import ( IssueViewSerializer, IssueSerializer, ) from plane.app.permissions import ( WorkspaceEntityPermission, ProjectEntityPermission, ) from plane.db.models import ( Workspace, IssueView, Issue, UserFavorite, IssueLink, IssueAttachment, ) from plane.utils.issue_filters import issue_filters from plane.utils.user_timezone_converter import user_timezone_converter class GlobalViewViewSet(BaseViewSet): serializer_class = IssueViewSerializer model = IssueView permission_classes = [ WorkspaceEntityPermission, ] def perform_create(self, serializer): workspace = Workspace.objects.get(slug=self.kwargs.get("slug")) serializer.save(workspace_id=workspace.id) def get_queryset(self): return self.filter_queryset( super() .get_queryset() .filter(workspace__slug=self.kwargs.get("slug")) .filter(project__isnull=True) .select_related("workspace") .order_by(self.request.GET.get("order_by", "-created_at")) .distinct() ) class GlobalViewIssuesViewSet(BaseViewSet): permission_classes = [ WorkspaceEntityPermission, ] def get_queryset(self): 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")) .filter( project__project_projectmember__member=self.request.user, project__project_projectmember__is_active=True, ) .select_related("workspace", "project", "state", "parent") .prefetch_related("assignees", "labels", "issue_module__module") .annotate(cycle_id=F("issue_cycle__cycle_id")) .annotate( link_count=IssueLink.objects.filter(issue=OuterRef("id")) .order_by() .annotate(count=Func(F("id"), function="Count")) .values("count") ) .annotate( attachment_count=IssueAttachment.objects.filter( issue=OuterRef("id") ) .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") ) .annotate( label_ids=Coalesce( ArrayAgg( "labels__id", distinct=True, filter=~Q(labels__id__isnull=True), ), Value([], output_field=ArrayField(UUIDField())), ), assignee_ids=Coalesce( ArrayAgg( "assignees__id", distinct=True, filter=~Q(assignees__id__isnull=True) & Q(assignees__member_project__is_active=True), ), Value([], output_field=ArrayField(UUIDField())), ), module_ids=Coalesce( ArrayAgg( "issue_module__module_id", distinct=True, filter=~Q(issue_module__module_id__isnull=True), ), Value([], output_field=ArrayField(UUIDField())), ), ) ) @method_decorator(gzip_page) def list(self, request, slug): filters = issue_filters(request.query_params, "GET") # Custom ordering for priority and state priority_order = ["urgent", "high", "medium", "low", "none"] state_order = [ "backlog", "unstarted", "started", "completed", "cancelled", ] order_by_param = request.GET.get("order_by", "-created_at") issue_queryset = ( self.get_queryset() .filter(**filters) .annotate(cycle_id=F("issue_cycle__cycle_id")) ) # Priority Ordering if order_by_param == "priority" or order_by_param == "-priority": priority_order = ( priority_order if order_by_param == "priority" else priority_order[::-1] ) issue_queryset = issue_queryset.annotate( priority_order=Case( *[ When(priority=p, then=Value(i)) for i, p in enumerate(priority_order) ], output_field=CharField(), ) ).order_by("priority_order") # State Ordering elif order_by_param in [ "state__name", "state__group", "-state__name", "-state__group", ]: state_order = ( state_order if order_by_param in ["state__name", "state__group"] else state_order[::-1] ) issue_queryset = issue_queryset.annotate( state_order=Case( *[ When(state__group=state_group, then=Value(i)) for i, state_group in enumerate(state_order) ], default=Value(len(state_order)), output_field=CharField(), ) ).order_by("state_order") # assignee and label ordering elif order_by_param in [ "labels__name", "-labels__name", "assignees__first_name", "-assignees__first_name", ]: issue_queryset = issue_queryset.annotate( max_values=Max( order_by_param[1::] if order_by_param.startswith("-") else order_by_param ) ).order_by( "-max_values" if order_by_param.startswith("-") else "max_values" ) else: issue_queryset = issue_queryset.order_by(order_by_param) if self.fields: issues = IssueSerializer( issue_queryset, many=True, fields=self.fields ).data else: issues = issue_queryset.values( "id", "name", "state_id", "sort_order", "completed_at", "estimate_point", "priority", "start_date", "target_date", "sequence_id", "project_id", "parent_id", "cycle_id", "module_ids", "label_ids", "assignee_ids", "sub_issues_count", "created_at", "updated_at", "created_by", "updated_by", "attachment_count", "link_count", "is_draft", "archived_at", ) datetime_fields = ["created_at", "updated_at"] issues = user_timezone_converter( issues, datetime_fields, request.user.user_timezone ) return Response(issues, status=status.HTTP_200_OK) class IssueViewViewSet(BaseViewSet): serializer_class = IssueViewSerializer model = IssueView permission_classes = [ ProjectEntityPermission, ] def perform_create(self, serializer): serializer.save(project_id=self.kwargs.get("project_id")) def get_queryset(self): subquery = UserFavorite.objects.filter( user=self.request.user, entity_identifier=OuterRef("pk"), entity_type="view", project_id=self.kwargs.get("project_id"), workspace__slug=self.kwargs.get("slug"), ) return self.filter_queryset( super() .get_queryset() .filter(workspace__slug=self.kwargs.get("slug")) .filter(project_id=self.kwargs.get("project_id")) .filter( project__project_projectmember__member=self.request.user, project__project_projectmember__is_active=True, project__archived_at__isnull=True, ) .select_related("project") .select_related("workspace") .annotate(is_favorite=Exists(subquery)) .order_by("-is_favorite", "name") .distinct() ) def list(self, request, slug, project_id): queryset = self.get_queryset() fields = [ field for field in request.GET.get("fields", "").split(",") if field ] views = IssueViewSerializer( queryset, many=True, fields=fields if fields else None ).data return Response(views, status=status.HTTP_200_OK) class IssueViewFavoriteViewSet(BaseViewSet): model = UserFavorite def get_queryset(self): return self.filter_queryset( super() .get_queryset() .filter(workspace__slug=self.kwargs.get("slug")) .filter(user=self.request.user) .select_related("view") ) def create(self, request, slug, project_id): _ = UserFavorite.objects.create( user=request.user, entity_identifier=request.data.get("view"), entity_type="view", project_id=project_id, ) return Response(status=status.HTTP_204_NO_CONTENT) def destroy(self, request, slug, project_id, view_id): view_favorite = UserFavorite.objects.get( project=project_id, user=request.user, workspace__slug=slug, entity_type="view", entity_identifier=view_id, ) view_favorite.delete() return Response(status=status.HTTP_204_NO_CONTENT)