Skip to content

Commit

Permalink
feat: my issues filtering (#1666)
Browse files Browse the repository at this point in the history
* feat: my issues filtering

* dev: migrations

* dev: remove state list endpoint

* dev: state group filtering
  • Loading branch information
pablohashescobar committed Jul 26, 2023
1 parent 39274fd commit fd9dcfa
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 28 deletions.
6 changes: 6 additions & 0 deletions apiserver/plane/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
UserIssueCompletedGraphEndpoint,
UserWorkspaceDashboardEndpoint,
WorkspaceThemeViewSet,
WorkspaceLabelsEndpoint,
## End Workspaces
# File Assets
FileAssetEndpoint,
Expand Down Expand Up @@ -385,6 +386,11 @@
),
name="workspace-themes",
),
path(
"workspaces/<str:slug>/labels/",
WorkspaceLabelsEndpoint.as_view(),
name="workspace-labels",
),
## End Workspaces ##
# Projects
path(
Expand Down
1 change: 1 addition & 0 deletions apiserver/plane/api/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
UserIssueCompletedGraphEndpoint,
UserWorkspaceDashboardEndpoint,
WorkspaceThemeViewSet,
WorkspaceLabelsEndpoint,
)
from .state import StateViewSet
from .view import IssueViewViewSet, ViewIssuesEndpoint, IssueViewFavoriteViewSet
Expand Down
103 changes: 85 additions & 18 deletions apiserver/plane/api/views/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
ProjectMemberLiteSerializer,
)
from plane.api.permissions import (
WorkspaceEntityPermission,
ProjectEntityPermission,
WorkSpaceAdminPermission,
ProjectMemberPermission,
Expand Down Expand Up @@ -157,7 +158,7 @@ def get_queryset(self):
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]
Expand Down Expand Up @@ -244,12 +245,6 @@ def list(self, request, slug, project_id):
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
Expand Down Expand Up @@ -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"))
Expand All @@ -333,7 +336,7 @@ def get(self, request, slug):
.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()
Expand All @@ -348,9 +351,77 @@ def get(self, request, slug):
.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(
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -667,9 +736,7 @@ def get(self, request, slug, project_id, issue_id):
)

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",
Expand Down
29 changes: 28 additions & 1 deletion apiserver/plane/api/views/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,14 @@
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


Expand Down Expand Up @@ -1009,3 +1015,24 @@ def create(self, request, slug):
{"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,
)

69 changes: 61 additions & 8 deletions apiserver/plane/db/migrations/0039_auto_20230723_2203.py
Original file line number Diff line number Diff line change
@@ -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")
Expand All @@ -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)


class Migration(migrations.Migration):
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),
),
]
27 changes: 26 additions & 1 deletion apiserver/plane/db/models/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,30 @@
)


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)
Expand Down Expand Up @@ -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"]
Expand Down
15 changes: 15 additions & 0 deletions apiserver/plane/utils/issue_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(",")
Expand Down Expand Up @@ -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(",")
Expand Down Expand Up @@ -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,
Expand Down

1 comment on commit fd9dcfa

@vercel
Copy link

@vercel vercel bot commented on fd9dcfa Jul 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

plane-dev – ./apps/app

plane-dev.vercel.app
plane-dev-git-develop-plane.vercel.app
plane-dev-plane.vercel.app

Please sign in to comment.