Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: my issues filtering #1666

Merged
merged 4 commits into from
Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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