forked from github/plane
feat: my issues filtering (#1666)
* feat: my issues filtering * dev: migrations * dev: remove state list endpoint * dev: state group filtering
This commit is contained in:
parent
39274fd5fa
commit
fd9dcfa2ec
@ -45,6 +45,7 @@ from plane.api.views import (
|
||||
UserIssueCompletedGraphEndpoint,
|
||||
UserWorkspaceDashboardEndpoint,
|
||||
WorkspaceThemeViewSet,
|
||||
WorkspaceLabelsEndpoint,
|
||||
## End Workspaces
|
||||
# File Assets
|
||||
FileAssetEndpoint,
|
||||
@ -385,6 +386,11 @@ urlpatterns = [
|
||||
),
|
||||
name="workspace-themes",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/labels/",
|
||||
WorkspaceLabelsEndpoint.as_view(),
|
||||
name="workspace-labels",
|
||||
),
|
||||
## End Workspaces ##
|
||||
# Projects
|
||||
path(
|
||||
|
@ -42,6 +42,7 @@ from .workspace import (
|
||||
UserIssueCompletedGraphEndpoint,
|
||||
UserWorkspaceDashboardEndpoint,
|
||||
WorkspaceThemeViewSet,
|
||||
WorkspaceLabelsEndpoint,
|
||||
)
|
||||
from .state import StateViewSet
|
||||
from .view import IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet
|
||||
|
@ -48,6 +48,7 @@ from plane.api.serializers import (
|
||||
ProjectMemberLiteSerializer,
|
||||
)
|
||||
from plane.api.permissions import (
|
||||
WorkspaceEntityPermission,
|
||||
ProjectEntityPermission,
|
||||
WorkSpaceAdminPermission,
|
||||
ProjectMemberPermission,
|
||||
@ -157,7 +158,7 @@ class IssueViewSet(BaseViewSet):
|
||||
def list(self, request, slug, project_id):
|
||||
try:
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
show_sub_issues = request.GET.get("show_sub_issues", "true")
|
||||
print(filters)
|
||||
|
||||
# Custom ordering for priority and state
|
||||
priority_order = ["urgent", "high", "medium", "low", None]
|
||||
@ -244,12 +245,6 @@ class IssueViewSet(BaseViewSet):
|
||||
else:
|
||||
issue_queryset = issue_queryset.order_by(order_by_param)
|
||||
|
||||
issue_queryset = (
|
||||
issue_queryset
|
||||
if show_sub_issues == "true"
|
||||
else issue_queryset.filter(parent__isnull=True)
|
||||
)
|
||||
|
||||
issues = IssueLiteSerializer(issue_queryset, many=True).data
|
||||
|
||||
## Grouping the results
|
||||
@ -317,9 +312,17 @@ class UserWorkSpaceIssues(BaseAPIView):
|
||||
@method_decorator(gzip_page)
|
||||
def get(self, request, slug):
|
||||
try:
|
||||
issues = (
|
||||
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 = (
|
||||
Issue.issue_objects.filter(
|
||||
assignees__in=[request.user], workspace__slug=slug
|
||||
(Q(assignees__in=[request.user]) | Q(created_by=request.user)),
|
||||
workspace__slug=slug,
|
||||
)
|
||||
.annotate(
|
||||
sub_issues_count=Issue.issue_objects.filter(parent=OuterRef("id"))
|
||||
@ -333,7 +336,7 @@ class UserWorkSpaceIssues(BaseAPIView):
|
||||
.select_related("parent")
|
||||
.prefetch_related("assignees")
|
||||
.prefetch_related("labels")
|
||||
.order_by("-created_at")
|
||||
.order_by(order_by_param)
|
||||
.annotate(
|
||||
link_count=IssueLink.objects.filter(issue=OuterRef("id"))
|
||||
.order_by()
|
||||
@ -348,9 +351,77 @@ class UserWorkSpaceIssues(BaseAPIView):
|
||||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.filter(**filters)
|
||||
)
|
||||
serializer = IssueLiteSerializer(issues, many=True)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
# 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)
|
||||
|
||||
issues = IssueLiteSerializer(issue_queryset, many=True).data
|
||||
|
||||
## Grouping the results
|
||||
group_by = request.GET.get("group_by", False)
|
||||
if group_by:
|
||||
return Response(
|
||||
group_results(issues, group_by), status=status.HTTP_200_OK
|
||||
)
|
||||
|
||||
return Response(issues, status=status.HTTP_200_OK)
|
||||
except Exception as e:
|
||||
capture_exception(e)
|
||||
return Response(
|
||||
@ -635,9 +706,7 @@ class SubIssuesEndpoint(BaseAPIView):
|
||||
def get(self, request, slug, project_id, issue_id):
|
||||
try:
|
||||
sub_issues = (
|
||||
Issue.issue_objects.filter(
|
||||
parent_id=issue_id, workspace__slug=slug
|
||||
)
|
||||
Issue.issue_objects.filter(parent_id=issue_id, workspace__slug=slug)
|
||||
.select_related("project")
|
||||
.select_related("workspace")
|
||||
.select_related("state")
|
||||
@ -667,9 +736,7 @@ class SubIssuesEndpoint(BaseAPIView):
|
||||
)
|
||||
|
||||
state_distribution = (
|
||||
State.objects.filter(
|
||||
~Q(name="Triage"), workspace__slug=slug
|
||||
)
|
||||
State.objects.filter(~Q(name="Triage"), workspace__slug=slug)
|
||||
.annotate(
|
||||
state_count=Count(
|
||||
"state_issue",
|
||||
|
@ -60,8 +60,14 @@ from plane.db.models import (
|
||||
PageFavorite,
|
||||
Page,
|
||||
IssueViewFavorite,
|
||||
Label,
|
||||
State,
|
||||
)
|
||||
from plane.api.permissions import (
|
||||
WorkSpaceBasePermission,
|
||||
WorkSpaceAdminPermission,
|
||||
WorkspaceEntityPermission,
|
||||
)
|
||||
from plane.api.permissions import WorkSpaceBasePermission, WorkSpaceAdminPermission
|
||||
from plane.bgtasks.workspace_invitation_task import workspace_invitation
|
||||
|
||||
|
||||
@ -1009,3 +1015,24 @@ class WorkspaceThemeViewSet(BaseViewSet):
|
||||
{"error": "Something went wrong please try again later"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
||||
class WorkspaceLabelsEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
WorkspaceEntityPermission,
|
||||
]
|
||||
|
||||
def get(self, request, slug):
|
||||
try:
|
||||
labels = Label.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project__project_projectmember__member=request.user,
|
||||
).values("parent", "name", "color", "id", "project_id", "workspace__slug")
|
||||
return Response(labels, status=status.HTTP_200_OK)
|
||||
except Exception as e:
|
||||
capture_exception(e)
|
||||
return Response(
|
||||
{"error": "Something went wrong please try again later"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
# Generated by Django 4.2.3 on 2023-07-23 16:33
|
||||
|
||||
from django.db import migrations
|
||||
from django.db import migrations, models
|
||||
import plane.db.models.workspace
|
||||
|
||||
|
||||
def rename_field(apps, schema_editor):
|
||||
Model = apps.get_model("db", "IssueActivity")
|
||||
@ -9,18 +11,69 @@ def rename_field(apps, schema_editor):
|
||||
obj.field = "assignees"
|
||||
updated_activity.append(obj)
|
||||
|
||||
Model.objects.bulk_update(
|
||||
updated_activity, ["field"], batch_size=100
|
||||
)
|
||||
|
||||
Model.objects.bulk_update(updated_activity, ["field"], batch_size=100)
|
||||
|
||||
|
||||
def update_workspace_member_props(apps, schema_editor):
|
||||
Model = apps.get_model("db", "WorkspaceMember")
|
||||
|
||||
updated_workspace_member = []
|
||||
|
||||
for obj in Model.objects.all():
|
||||
if obj.view_props is None:
|
||||
obj.view_props = {
|
||||
"filters": {"type": None},
|
||||
"groupByProperty": None,
|
||||
"issueView": "list",
|
||||
"orderBy": "-created_at",
|
||||
"properties": {
|
||||
"assignee": True,
|
||||
"due_date": True,
|
||||
"key": True,
|
||||
"labels": True,
|
||||
"priority": True,
|
||||
"state": True,
|
||||
"sub_issue_count": True,
|
||||
"attachment_count": True,
|
||||
"link": True,
|
||||
"estimate": True,
|
||||
"created_on": True,
|
||||
"updated_on": True,
|
||||
},
|
||||
"showEmptyGroups": True,
|
||||
}
|
||||
else:
|
||||
current_view_props = obj.view_props
|
||||
obj.view_props = {
|
||||
"filters": {"type": None},
|
||||
"groupByProperty": None,
|
||||
"issueView": "list",
|
||||
"orderBy": "-created_at",
|
||||
"showEmptyGroups": True,
|
||||
"properties": current_view_props,
|
||||
}
|
||||
|
||||
updated_workspace_member.append(obj)
|
||||
|
||||
Model.objects.bulk_update(updated_workspace_member, ["view_props"], batch_size=100)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('db', '0038_auto_20230720_1505'),
|
||||
("db", "0038_auto_20230720_1505"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(rename_field)
|
||||
migrations.RunPython(rename_field),
|
||||
migrations.RunPython(update_workspace_member_props),
|
||||
migrations.AlterField(
|
||||
model_name='workspacemember',
|
||||
name='view_props',
|
||||
field=models.JSONField(default=plane.db.models.workspace.get_default_props),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='workspacemember',
|
||||
name='default_props',
|
||||
field=models.JSONField(default=plane.db.models.workspace.get_default_props),
|
||||
),
|
||||
]
|
||||
|
@ -14,6 +14,30 @@ ROLE_CHOICES = (
|
||||
)
|
||||
|
||||
|
||||
def get_default_props():
|
||||
return {
|
||||
"filters": {"type": None},
|
||||
"groupByProperty": None,
|
||||
"issueView": "list",
|
||||
"orderBy": "-created_at",
|
||||
"properties": {
|
||||
"assignee": True,
|
||||
"due_date": True,
|
||||
"key": True,
|
||||
"labels": True,
|
||||
"priority": True,
|
||||
"state": True,
|
||||
"sub_issue_count": True,
|
||||
"attachment_count": True,
|
||||
"link": True,
|
||||
"estimate": True,
|
||||
"created_on": True,
|
||||
"updated_on": True,
|
||||
},
|
||||
"showEmptyGroups": True,
|
||||
}
|
||||
|
||||
|
||||
class Workspace(BaseModel):
|
||||
name = models.CharField(max_length=80, verbose_name="Workspace Name")
|
||||
logo = models.URLField(verbose_name="Logo", blank=True, null=True)
|
||||
@ -47,7 +71,8 @@ class WorkspaceMember(BaseModel):
|
||||
)
|
||||
role = models.PositiveSmallIntegerField(choices=ROLE_CHOICES, default=10)
|
||||
company_role = models.TextField(null=True, blank=True)
|
||||
view_props = models.JSONField(null=True, blank=True)
|
||||
view_props = models.JSONField(default=get_default_props)
|
||||
default_props = models.JSONField(default=get_default_props)
|
||||
|
||||
class Meta:
|
||||
unique_together = ["workspace", "member"]
|
||||
|
@ -12,6 +12,18 @@ def filter_state(params, filter, method):
|
||||
return filter
|
||||
|
||||
|
||||
def filter_state_group(params, filter, method):
|
||||
if method == "GET":
|
||||
state_group = params.get("state_group").split(",")
|
||||
if len(state_group) and "" not in state_group:
|
||||
filter["state__group__in"] = state_group
|
||||
else:
|
||||
if params.get("state_group", None) and len(params.get("state_group")):
|
||||
filter["state__group__in"] = params.get("state_group")
|
||||
return filter
|
||||
|
||||
|
||||
|
||||
def filter_estimate_point(params, filter, method):
|
||||
if method == "GET":
|
||||
estimate_points = params.get("estimate_point").split(",")
|
||||
@ -212,6 +224,7 @@ def filter_issue_state_type(params, filter, method):
|
||||
return filter
|
||||
|
||||
|
||||
|
||||
def filter_project(params, filter, method):
|
||||
if method == "GET":
|
||||
projects = params.get("project").split(",")
|
||||
@ -270,9 +283,11 @@ def filter_sub_issue_toggle(params, filter, method):
|
||||
|
||||
def issue_filters(query_params, method):
|
||||
filter = dict()
|
||||
print(query_params)
|
||||
|
||||
ISSUE_FILTER = {
|
||||
"state": filter_state,
|
||||
"state_group": filter_state_group,
|
||||
"estimate_point": filter_estimate_point,
|
||||
"priority": filter_priority,
|
||||
"parent": filter_parent,
|
||||
|
Loading…
Reference in New Issue
Block a user