mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
dev: reduce module endpoints and create a new endpoint for getting issues by list
This commit is contained in:
parent
bac8aeb4ad
commit
042cc3160c
@ -2,6 +2,7 @@ from django.urls import path
|
|||||||
|
|
||||||
|
|
||||||
from plane.app.views import (
|
from plane.app.views import (
|
||||||
|
IssueListEndpoint,
|
||||||
IssueViewSet,
|
IssueViewSet,
|
||||||
LabelViewSet,
|
LabelViewSet,
|
||||||
BulkCreateIssueLabelsEndpoint,
|
BulkCreateIssueLabelsEndpoint,
|
||||||
@ -25,6 +26,11 @@ from plane.app.views import (
|
|||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/list/",
|
||||||
|
IssueListEndpoint.as_view(),
|
||||||
|
name="project-issue",
|
||||||
|
),
|
||||||
path(
|
path(
|
||||||
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/",
|
"workspaces/<str:slug>/projects/<uuid:project_id>/issues/",
|
||||||
IssueViewSet.as_view(
|
IssueViewSet.as_view(
|
||||||
|
@ -67,6 +67,7 @@ from .cycle import (
|
|||||||
)
|
)
|
||||||
from .asset import FileAssetEndpoint, UserAssetsEndpoint, FileAssetViewSet
|
from .asset import FileAssetEndpoint, UserAssetsEndpoint, FileAssetViewSet
|
||||||
from .issue import (
|
from .issue import (
|
||||||
|
IssueListEndpoint,
|
||||||
IssueViewSet,
|
IssueViewSet,
|
||||||
WorkSpaceIssuesEndpoint,
|
WorkSpaceIssuesEndpoint,
|
||||||
IssueActivityEndpoint,
|
IssueActivityEndpoint,
|
||||||
|
@ -4,7 +4,6 @@ import random
|
|||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
# Django imports
|
# Django imports
|
||||||
from django.db import models
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.db.models import (
|
from django.db.models import (
|
||||||
Prefetch,
|
Prefetch,
|
||||||
@ -82,6 +81,137 @@ from plane.utils.grouper import group_results
|
|||||||
from plane.utils.issue_filters import issue_filters
|
from plane.utils.issue_filters import issue_filters
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
|
class IssueListEndpoint(BaseAPIView):
|
||||||
|
|
||||||
|
permission_classes = [
|
||||||
|
ProjectEntityPermission,
|
||||||
|
]
|
||||||
|
|
||||||
|
def post(self, request, slug, project_id):
|
||||||
|
issues = request.data.get("issues", [])
|
||||||
|
|
||||||
|
if issues:
|
||||||
|
return Response(
|
||||||
|
{"error": "Issues are required"},
|
||||||
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
|
)
|
||||||
|
|
||||||
|
queryset = (
|
||||||
|
Issue.issue_objects.filter(
|
||||||
|
workspace__slug=slug, project_id=project_id, pk__in=issues
|
||||||
|
)
|
||||||
|
.select_related("workspace", "project", "state", "parent")
|
||||||
|
.prefetch_related("assignees", "labels", "issue_module__module")
|
||||||
|
.prefetch_related(
|
||||||
|
Prefetch(
|
||||||
|
"issue_reactions",
|
||||||
|
queryset=IssueReaction.objects.select_related("actor"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.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")
|
||||||
|
)
|
||||||
|
).distinct()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
|
||||||
|
serializer = IssueSerializer(queryset, many=True, fields=self.fields, expand=self.expand)
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class IssueViewSet(WebhookMixin, BaseViewSet):
|
class IssueViewSet(WebhookMixin, BaseViewSet):
|
||||||
def get_serializer_class(self):
|
def get_serializer_class(self):
|
||||||
|
@ -4,10 +4,8 @@ import json
|
|||||||
# Django Imports
|
# Django Imports
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.db.models import Prefetch, F, OuterRef, Func, Exists, Count, Q
|
from django.db.models import Prefetch, F, OuterRef, Func, Exists, Count, Q
|
||||||
from django.core import serializers
|
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.views.decorators.gzip import gzip_page
|
from django.views.decorators.gzip import gzip_page
|
||||||
from django.core.serializers.json import DjangoJSONEncoder
|
|
||||||
|
|
||||||
|
|
||||||
# Third party imports
|
# Third party imports
|
||||||
@ -38,11 +36,9 @@ from plane.db.models import (
|
|||||||
ModuleFavorite,
|
ModuleFavorite,
|
||||||
IssueLink,
|
IssueLink,
|
||||||
IssueAttachment,
|
IssueAttachment,
|
||||||
IssueSubscriber,
|
|
||||||
ModuleUserProperties,
|
ModuleUserProperties,
|
||||||
)
|
)
|
||||||
from plane.bgtasks.issue_activites_task import issue_activity
|
from plane.bgtasks.issue_activites_task import issue_activity
|
||||||
from plane.utils.grouper import group_results
|
|
||||||
from plane.utils.issue_filters import issue_filters
|
from plane.utils.issue_filters import issue_filters
|
||||||
from plane.utils.analytics_plot import burndown_plot
|
from plane.utils.analytics_plot import burndown_plot
|
||||||
|
|
||||||
@ -62,7 +58,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
subquery = ModuleFavorite.objects.filter(
|
favorite_subquery = ModuleFavorite.objects.filter(
|
||||||
user=self.request.user,
|
user=self.request.user,
|
||||||
module_id=OuterRef("pk"),
|
module_id=OuterRef("pk"),
|
||||||
project_id=self.kwargs.get("project_id"),
|
project_id=self.kwargs.get("project_id"),
|
||||||
@ -73,7 +69,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
|
|||||||
.get_queryset()
|
.get_queryset()
|
||||||
.filter(project_id=self.kwargs.get("project_id"))
|
.filter(project_id=self.kwargs.get("project_id"))
|
||||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||||
.annotate(is_favorite=Exists(subquery))
|
.annotate(is_favorite=Exists(favorite_subquery))
|
||||||
.select_related("project")
|
.select_related("project")
|
||||||
.select_related("workspace")
|
.select_related("workspace")
|
||||||
.select_related("lead")
|
.select_related("lead")
|
||||||
@ -331,17 +327,16 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
ProjectEntityPermission,
|
ProjectEntityPermission,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return (
|
return (
|
||||||
Issue.issue_objects.filter(
|
Issue.issue_objects.filter(
|
||||||
project_id=self.kwargs.get("project_id"),
|
project_id=self.kwargs.get("project_id"),
|
||||||
workspace__slug=self.kwargs.get("slug"),
|
workspace__slug=self.kwargs.get("slug"),
|
||||||
issue_module__module_id=self.kwargs.get("module_id")
|
issue_module__module_id=self.kwargs.get("module_id"),
|
||||||
)
|
)
|
||||||
.select_related("workspace", "project", "state", "parent")
|
.select_related("workspace", "project", "state", "parent")
|
||||||
.prefetch_related("labels", "assignees")
|
.prefetch_related("labels", "assignees")
|
||||||
.prefetch_related('issue_module__module')
|
.prefetch_related("issue_module__module")
|
||||||
.annotate(cycle_id=F("issue_cycle__cycle_id"))
|
.annotate(cycle_id=F("issue_cycle__cycle_id"))
|
||||||
.annotate(
|
.annotate(
|
||||||
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
||||||
@ -384,7 +379,7 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
# create multiple issues inside a module
|
# create multiple issues inside a module
|
||||||
def create_module_issues(self, request, slug, project_id, module_id):
|
def create_module_issues(self, request, slug, project_id, module_id):
|
||||||
issues = request.data.get("issues", [])
|
issues = request.data.get("issues", [])
|
||||||
if not len(issues):
|
if not issues:
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "Issues are required"},
|
{"error": "Issues are required"},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
@ -420,15 +415,12 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
)
|
)
|
||||||
for issue in issues
|
for issue in issues
|
||||||
]
|
]
|
||||||
issues = (self.get_queryset().filter(pk__in=issues))
|
return Response({"message": "success"}, status=status.HTTP_201_CREATED)
|
||||||
serializer = IssueSerializer(issues , many=True)
|
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
|
||||||
|
|
||||||
|
|
||||||
# create multiple module inside an issue
|
# create multiple module inside an issue
|
||||||
def create_issue_modules(self, request, slug, project_id, issue_id):
|
def create_issue_modules(self, request, slug, project_id, issue_id):
|
||||||
modules = request.data.get("modules", [])
|
modules = request.data.get("modules", [])
|
||||||
if not len(modules):
|
if not modules:
|
||||||
return Response(
|
return Response(
|
||||||
{"error": "Modules are required"},
|
{"error": "Modules are required"},
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
@ -466,10 +458,7 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
for module in modules
|
for module in modules
|
||||||
]
|
]
|
||||||
|
|
||||||
issue = (self.get_queryset().filter(pk=issue_id).first())
|
return Response({"message": "success"}, status=status.HTTP_201_CREATED)
|
||||||
serializer = IssueSerializer(issue)
|
|
||||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
|
||||||
|
|
||||||
|
|
||||||
def destroy(self, request, slug, project_id, module_id, issue_id):
|
def destroy(self, request, slug, project_id, module_id, issue_id):
|
||||||
module_issue = ModuleIssue.objects.get(
|
module_issue = ModuleIssue.objects.get(
|
||||||
@ -484,7 +473,9 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet):
|
|||||||
actor_id=str(request.user.id),
|
actor_id=str(request.user.id),
|
||||||
issue_id=str(issue_id),
|
issue_id=str(issue_id),
|
||||||
project_id=str(project_id),
|
project_id=str(project_id),
|
||||||
current_instance=json.dumps({"module_name": module_issue.module.name}),
|
current_instance=json.dumps(
|
||||||
|
{"module_name": module_issue.module.name}
|
||||||
|
),
|
||||||
epoch=int(timezone.now().timestamp()),
|
epoch=int(timezone.now().timestamp()),
|
||||||
notification=True,
|
notification=True,
|
||||||
origin=request.META.get("HTTP_ORIGIN"),
|
origin=request.META.get("HTTP_ORIGIN"),
|
||||||
|
Loading…
Reference in New Issue
Block a user