feat: issue search endpoint (#667)

* feat: issue search endpoints

* dev: update issue search for blocker and blocked by
This commit is contained in:
pablohashescobar 2023-04-05 00:17:16 +05:30 committed by GitHub
parent 8cbf75ad6c
commit ff5cddeb95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 160 additions and 61 deletions

View File

@ -133,6 +133,7 @@ from plane.api.views import (
## End importer ## End importer
# Search # Search
GlobalSearchEndpoint, GlobalSearchEndpoint,
IssueSearchEndpoint,
## End Search ## End Search
# Gpt # Gpt
GPTIntegrationEndpoint, GPTIntegrationEndpoint,
@ -1170,6 +1171,11 @@ urlpatterns = [
GlobalSearchEndpoint.as_view(), GlobalSearchEndpoint.as_view(),
name="global-search", name="global-search",
), ),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/search-issues/",
IssueSearchEndpoint.as_view(),
name="project-issue-search",
),
## End Search ## End Search
# Gpt # Gpt
path( path(

View File

@ -125,7 +125,7 @@ from .page import (
CreatedbyOtherPagesEndpoint, CreatedbyOtherPagesEndpoint,
) )
from .search import GlobalSearchEndpoint from .search import GlobalSearchEndpoint, IssueSearchEndpoint
from .gpt import GPTIntegrationEndpoint from .gpt import GPTIntegrationEndpoint

View File

@ -12,6 +12,7 @@ from sentry_sdk import capture_exception
# Module imports # Module imports
from .base import BaseAPIView from .base import BaseAPIView
from plane.db.models import Workspace, Project, Issue, Cycle, Module, Page, IssueView from plane.db.models import Workspace, Project, Issue, Cycle, Module, Page, IssueView
from plane.utils.issue_search import search_issues
class GlobalSearchEndpoint(BaseAPIView): class GlobalSearchEndpoint(BaseAPIView):
@ -24,20 +25,26 @@ class GlobalSearchEndpoint(BaseAPIView):
q = Q() q = Q()
for field in fields: for field in fields:
q |= Q(**{f"{field}__icontains": query}) q |= Q(**{f"{field}__icontains": query})
return Workspace.objects.filter( return (
q, workspace_member__member=self.request.user Workspace.objects.filter(q, workspace_member__member=self.request.user)
).distinct().values("name", "id", "slug") .distinct()
.values("name", "id", "slug")
)
def filter_projects(self, query, slug, project_id): def filter_projects(self, query, slug, project_id):
fields = ["name"] fields = ["name"]
q = Q() q = Q()
for field in fields: for field in fields:
q |= Q(**{f"{field}__icontains": query}) q |= Q(**{f"{field}__icontains": query})
return Project.objects.filter( return (
q, Project.objects.filter(
Q(project_projectmember__member=self.request.user) | Q(network=2), q,
workspace__slug=slug, Q(project_projectmember__member=self.request.user) | Q(network=2),
).distinct().values("name", "id", "identifier", "workspace__slug") workspace__slug=slug,
)
.distinct()
.values("name", "id", "identifier", "workspace__slug")
)
def filter_issues(self, query, slug, project_id): def filter_issues(self, query, slug, project_id):
fields = ["name", "sequence_id"] fields = ["name", "sequence_id"]
@ -49,18 +56,22 @@ class GlobalSearchEndpoint(BaseAPIView):
q |= Q(**{"sequence_id": sequence_id}) q |= Q(**{"sequence_id": sequence_id})
else: else:
q |= Q(**{f"{field}__icontains": query}) q |= Q(**{f"{field}__icontains": query})
return Issue.objects.filter( return (
q, Issue.objects.filter(
project__project_projectmember__member=self.request.user, q,
workspace__slug=slug, project__project_projectmember__member=self.request.user,
project_id=project_id, workspace__slug=slug,
).distinct().values( project_id=project_id,
"name", )
"id", .distinct()
"sequence_id", .values(
"project__identifier", "name",
"project_id", "id",
"workspace__slug", "sequence_id",
"project__identifier",
"project_id",
"workspace__slug",
)
) )
def filter_cycles(self, query, slug, project_id): def filter_cycles(self, query, slug, project_id):
@ -68,16 +79,20 @@ class GlobalSearchEndpoint(BaseAPIView):
q = Q() q = Q()
for field in fields: for field in fields:
q |= Q(**{f"{field}__icontains": query}) q |= Q(**{f"{field}__icontains": query})
return Cycle.objects.filter( return (
q, Cycle.objects.filter(
project__project_projectmember__member=self.request.user, q,
workspace__slug=slug, project__project_projectmember__member=self.request.user,
project_id=project_id, workspace__slug=slug,
).distinct().values( project_id=project_id,
"name", )
"id", .distinct()
"project_id", .values(
"workspace__slug", "name",
"id",
"project_id",
"workspace__slug",
)
) )
def filter_modules(self, query, slug, project_id): def filter_modules(self, query, slug, project_id):
@ -85,16 +100,20 @@ class GlobalSearchEndpoint(BaseAPIView):
q = Q() q = Q()
for field in fields: for field in fields:
q |= Q(**{f"{field}__icontains": query}) q |= Q(**{f"{field}__icontains": query})
return Module.objects.filter( return (
q, Module.objects.filter(
project__project_projectmember__member=self.request.user, q,
workspace__slug=slug, project__project_projectmember__member=self.request.user,
project_id=project_id, workspace__slug=slug,
).distinct().values( project_id=project_id,
"name", )
"id", .distinct()
"project_id", .values(
"workspace__slug", "name",
"id",
"project_id",
"workspace__slug",
)
) )
def filter_pages(self, query, slug, project_id): def filter_pages(self, query, slug, project_id):
@ -102,16 +121,20 @@ class GlobalSearchEndpoint(BaseAPIView):
q = Q() q = Q()
for field in fields: for field in fields:
q |= Q(**{f"{field}__icontains": query}) q |= Q(**{f"{field}__icontains": query})
return Page.objects.filter( return (
q, Page.objects.filter(
project__project_projectmember__member=self.request.user, q,
workspace__slug=slug, project__project_projectmember__member=self.request.user,
project_id=project_id, workspace__slug=slug,
).distinct().values( project_id=project_id,
"name", )
"id", .distinct()
"project_id", .values(
"workspace__slug", "name",
"id",
"project_id",
"workspace__slug",
)
) )
def filter_views(self, query, slug, project_id): def filter_views(self, query, slug, project_id):
@ -119,16 +142,20 @@ class GlobalSearchEndpoint(BaseAPIView):
q = Q() q = Q()
for field in fields: for field in fields:
q |= Q(**{f"{field}__icontains": query}) q |= Q(**{f"{field}__icontains": query})
return IssueView.objects.filter( return (
q, IssueView.objects.filter(
project__project_projectmember__member=self.request.user, q,
workspace__slug=slug, project__project_projectmember__member=self.request.user,
project_id=project_id, workspace__slug=slug,
).distinct().values( project_id=project_id,
"name", )
"id", .distinct()
"project_id", .values(
"workspace__slug", "name",
"id",
"project_id",
"workspace__slug",
)
) )
def get(self, request, slug, project_id): def get(self, request, slug, project_id):
@ -173,3 +200,46 @@ class GlobalSearchEndpoint(BaseAPIView):
{"error": "Something went wrong please try again later"}, {"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
class IssueSearchEndpoint(BaseAPIView):
def get(self, request, slug, project_id):
try:
query = request.query_params.get("search", False)
parent = request.query_params.get("parent", False)
blocker_blocked_by = request.query_params.get("blocker_blocked_by", False)
issue_id = request.query_params.get("issue_id", False)
issues = search_issues(query)
issues = issues.filter(
workspace__slug=slug,
project_id=project_id,
project__project_projectmember__member=self.request.user,
)
if parent:
issues.filter(parent__isnull=True)
if blocker_blocked_by and issue_id:
issues.filter(blocker_issues=issue_id, blocked_issues=issue_id)
return Response(
issues.values(
"name",
"id",
"sequence_id",
"project__identifier",
"project_id",
"workspace__slug",
),
status=status.HTTP_200_OK,
)
except Issue.DoesNotExist:
return Response(
{"error": "Issue Does not exist"}, status=status.HTTP_400_BAD_REQUEST
)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)

View File

@ -0,0 +1,23 @@
# Python imports
import re
# Django imports
from django.db.models import Q
# Module imports
from plane.db.models import Issue
def search_issues(query):
fields = ["name", "sequence_id"]
q = Q()
for field in fields:
if field == "sequence_id":
sequences = re.findall(r"\d+\.\d+|\d+", query)
for sequence_id in sequences:
q |= Q(**{"sequence_id": sequence_id})
else:
q |= Q(**{f"{field}__icontains": query})
return Issue.objects.filter(
q,
).distinct()