dev: paginate single entities

This commit is contained in:
pablohashescobar 2024-02-27 19:55:21 +05:30
parent 837193cda6
commit e82d7a2aa8
3 changed files with 49 additions and 39 deletions

View File

@ -77,6 +77,7 @@ from plane.utils.issue_filters import issue_filters
from plane.utils.order_queryset import order_issue_queryset from plane.utils.order_queryset import order_issue_queryset
from plane.utils.paginator import GroupedOffsetPaginator from plane.utils.paginator import GroupedOffsetPaginator
class IssueListEndpoint(BaseAPIView): class IssueListEndpoint(BaseAPIView):
permission_classes = [ permission_classes = [
@ -339,22 +340,24 @@ class IssueViewSet(WebhookMixin, BaseViewSet):
"archived_at", "archived_at",
) )
if request.GET.get("layout", "spreadsheet") in [ # Group by
"gantt", group_by = request.GET.get("group_by", False)
"spreadsheet",
]: # List Paginate
if not group_by:
return self.paginate( return self.paginate(
request=request, queryset=issue_queryset, on_results=on_results request=request, queryset=issue_queryset, on_results=on_results
) )
# Group paginate
return self.paginate( return self.paginate(
request=request, request=request,
queryset=issue_queryset, queryset=issue_queryset,
on_results=on_results, on_results=on_results,
paginator_cls=GroupedOffsetPaginator, paginator_cls=GroupedOffsetPaginator,
group_by_field_name="priority", group_by_field_name=group_by,
group_by_fields=issue_grouper( group_by_fields=issue_grouper(
field="priority", slug=slug, project_id=project_id field=group_by, slug=slug, project_id=project_id
), ),
) )
@ -2018,7 +2021,9 @@ class IssueDraftViewSet(BaseViewSet):
status=status.HTTP_404_NOT_FOUND, status=status.HTTP_404_NOT_FOUND,
) )
serializer = IssueCreateSerializer(issue, data=request.data, partial=True) serializer = IssueCreateSerializer(
issue, data=request.data, partial=True
)
if serializer.is_valid(): if serializer.is_valid():
serializer.save() serializer.save()

View File

@ -1,3 +1,7 @@
# Django imports
from django.db.models import Q
# Module imports
from plane.db.models import State, Label, ProjectMember, Cycle, Module from plane.db.models import State, Label, ProjectMember, Cycle, Module
@ -244,19 +248,21 @@ def group_results(results_data, group_by, sub_group_by=False):
def issue_grouper(field, slug, project_id): def issue_grouper(field, slug, project_id):
if field == "state": if field == "state_id":
return list( return list(
State.objects.filter( State.objects.filter(
workspace__slug=slug, project_id=project_id ~Q(name="Triage"),
workspace__slug=slug,
project_id=project_id,
).values_list("id", flat=True) ).values_list("id", flat=True)
) )
if field == "labels": if field == "label_ids":
return list( return list(
Label.objects.filter( Label.objects.filter(
workspace__slug=slug, project_id=project_id workspace__slug=slug, project_id=project_id
).values_list("id", flat=True) ).values_list("id", flat=True)
) )
if field == "assignees": if field == "assignee_ids":
return list( return list(
ProjectMember.objects.filter( ProjectMember.objects.filter(
workspace__slug=slug, project_id=project_id workspace__slug=slug, project_id=project_id
@ -271,13 +277,13 @@ def issue_grouper(field, slug, project_id):
workspace__slug=slug, project_id=project_id workspace__slug=slug, project_id=project_id
).values_list("member_id", flat=True) ).values_list("member_id", flat=True)
) )
if field == "cycle": if field == "cycle_id":
return list( return list(
Cycle.objects.filter( Cycle.objects.filter(
workspace__slug=slug, project_id=project_id workspace__slug=slug, project_id=project_id
).values_list("id", flat=True) ).values_list("id", flat=True)
) )
if field == "module": if field == "module_ids":
return list( return list(
Module.objects.filter( Module.objects.filter(
workspace__slug=slug, project_id=project_id workspace__slug=slug, project_id=project_id

View File

@ -4,7 +4,7 @@ from collections.abc import Sequence
# Django imports # Django imports
from django.db.models import Window, F, Count, Q from django.db.models import Window, F, Count, Q
from django.db.models.functions import RowNumber from django.db.models.functions import RowNumber, DenseRank
# Third party imports # Third party imports
from rest_framework.response import Response from rest_framework.response import Response
@ -149,6 +149,9 @@ class OffsetPaginator:
max_hits=max_hits, max_hits=max_hits,
) )
def process_results(self, results):
raise NotImplementedError
class GroupedOffsetPaginator(OffsetPaginator): class GroupedOffsetPaginator(OffsetPaginator):
def __init__( def __init__(
@ -186,6 +189,10 @@ class GroupedOffsetPaginator(OffsetPaginator):
# Compute the results # Compute the results
results = {} results = {}
queryset = queryset.annotate( queryset = queryset.annotate(
# group_rank=Window(
# expression=DenseRank(),
# order_by=F(self.group_by_field_name).asc()
# ),
row_number=Window( row_number=Window(
expression=RowNumber(), expression=RowNumber(),
partition_by=[F(self.group_by_field_name)], partition_by=[F(self.group_by_field_name)],
@ -193,6 +200,7 @@ class GroupedOffsetPaginator(OffsetPaginator):
) )
) )
# Filter the results
results = queryset.filter(row_number__gte=offset, row_number__lt=stop) results = queryset.filter(row_number__gte=offset, row_number__lt=stop)
# Adjust cursors based on the grouped results for pagination # Adjust cursors based on the grouped results for pagination
@ -212,7 +220,7 @@ class GroupedOffsetPaginator(OffsetPaginator):
# Optionally, calculate the total count and max_hits if needed # Optionally, calculate the total count and max_hits if needed
# This might require adjustments based on specific use cases # This might require adjustments based on specific use cases
max_hits = math.ceil( max_hits = math.ceil(
self.queryset.values(self.group_by_field_name) queryset.values(self.group_by_field_name)
.annotate( .annotate(
count=Count( count=Count(
self.group_by_field_name, self.group_by_field_name,
@ -221,7 +229,6 @@ class GroupedOffsetPaginator(OffsetPaginator):
.order_by("-count")[0]["count"] .order_by("-count")[0]["count"]
/ limit / limit
) )
return CursorResult( return CursorResult(
results=results, results=results,
next=next_cursor, next=next_cursor,
@ -230,6 +237,17 @@ class GroupedOffsetPaginator(OffsetPaginator):
max_hits=max_hits, max_hits=max_hits,
) )
def process_results(self, results):
processed_results = {}
for result in results:
group_value = str(result.get(self.group_by_field_name))
if group_value not in processed_results:
processed_results[str(group_value)] = {
"results": [],
}
processed_results[str(group_value)]["results"].append(result)
return processed_results
class BasePaginator: class BasePaginator:
"""BasePaginator class can be inherited by any View to return a paginated view""" """BasePaginator class can be inherited by any View to return a paginated view"""
@ -252,18 +270,6 @@ class BasePaginator:
return per_page return per_page
def get_layout(self, request):
layout = request.GET.get("layout", "list")
if layout not in [
"list",
"kanban",
"spreadsheet",
"calendar",
"gantt",
]:
raise ValidationError(detail="Invalid layout given")
return layout
def paginate( def paginate(
self, self,
request, request,
@ -292,8 +298,9 @@ class BasePaginator:
raise ParseError(detail="Invalid cursor parameter.") raise ParseError(detail="Invalid cursor parameter.")
if not paginator: if not paginator:
paginator_kwargs["group_by_fields"] = group_by_fields if group_by_fields and group_by_field_name:
paginator_kwargs["group_by_field_name"] = group_by_field_name paginator_kwargs["group_by_fields"] = group_by_fields
paginator_kwargs["group_by_field_name"] = group_by_field_name
paginator = paginator_cls(**paginator_kwargs) paginator = paginator_cls(**paginator_kwargs)
try: try:
@ -306,17 +313,9 @@ class BasePaginator:
if on_results: if on_results:
results = on_results(cursor_result.results) results = on_results(cursor_result.results)
processed_results = {}
if group_by_field_name and group_by_fields: if group_by_field_name and group_by_fields:
for result in results: results = paginator.process_results(results=results)
group_value = str(result.get(group_by_field_name))
if group_value not in processed_results:
processed_results[str(group_value)] = {
"results": [],
}
processed_results[str(group_value)]["results"].append(result)
results = processed_results
# Add Manipulation functions to the response # Add Manipulation functions to the response
if controller is not None: if controller is not None:
results = controller(results) results = controller(results)