From de3cf157862dfd8edb49c390e2ce5956e35bc31e Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 1 Apr 2023 00:20:27 +0530 Subject: [PATCH 1/2] feat: issue search endpoints --- apiserver/plane/api/urls.py | 6 + apiserver/plane/api/views/__init__.py | 2 +- apiserver/plane/api/views/search.py | 179 +++++++++++++++++--------- apiserver/plane/utils/issue_search.py | 23 ++++ 4 files changed, 149 insertions(+), 61 deletions(-) create mode 100644 apiserver/plane/utils/issue_search.py diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index d408be37e56..800f584c844 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -133,6 +133,7 @@ ## End importer # Search GlobalSearchEndpoint, + IssueSearchEndpoint, ## End Search # Gpt GPTIntegrationEndpoint, @@ -1170,6 +1171,11 @@ GlobalSearchEndpoint.as_view(), name="global-search", ), + path( + "workspaces//projects//search-issues/", + IssueSearchEndpoint.as_view(), + name="project-issue-search", + ), ## End Search # Gpt path( diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index b6171d68ba4..f84e78a1639 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -125,7 +125,7 @@ CreatedbyOtherPagesEndpoint, ) -from .search import GlobalSearchEndpoint +from .search import GlobalSearchEndpoint, IssueSearchEndpoint from .gpt import GPTIntegrationEndpoint diff --git a/apiserver/plane/api/views/search.py b/apiserver/plane/api/views/search.py index ba75eac91dd..fe5110a8836 100644 --- a/apiserver/plane/api/views/search.py +++ b/apiserver/plane/api/views/search.py @@ -12,6 +12,7 @@ # Module imports from .base import BaseAPIView from plane.db.models import Workspace, Project, Issue, Cycle, Module, Page, IssueView +from plane.utils.issue_search import search_issues class GlobalSearchEndpoint(BaseAPIView): @@ -24,20 +25,26 @@ def filter_workspaces(self, query, slug, project_id): q = Q() for field in fields: q |= Q(**{f"{field}__icontains": query}) - return Workspace.objects.filter( - q, workspace_member__member=self.request.user - ).distinct().values("name", "id", "slug") + return ( + Workspace.objects.filter(q, workspace_member__member=self.request.user) + .distinct() + .values("name", "id", "slug") + ) def filter_projects(self, query, slug, project_id): fields = ["name"] q = Q() for field in fields: q |= Q(**{f"{field}__icontains": query}) - return Project.objects.filter( - q, - Q(project_projectmember__member=self.request.user) | Q(network=2), - workspace__slug=slug, - ).distinct().values("name", "id", "identifier", "workspace__slug") + return ( + Project.objects.filter( + q, + Q(project_projectmember__member=self.request.user) | Q(network=2), + workspace__slug=slug, + ) + .distinct() + .values("name", "id", "identifier", "workspace__slug") + ) def filter_issues(self, query, slug, project_id): fields = ["name", "sequence_id"] @@ -49,18 +56,22 @@ def filter_issues(self, query, slug, project_id): q |= Q(**{"sequence_id": sequence_id}) else: q |= Q(**{f"{field}__icontains": query}) - return Issue.objects.filter( - q, - project__project_projectmember__member=self.request.user, - workspace__slug=slug, - project_id=project_id, - ).distinct().values( - "name", - "id", - "sequence_id", - "project__identifier", - "project_id", - "workspace__slug", + return ( + Issue.objects.filter( + q, + project__project_projectmember__member=self.request.user, + workspace__slug=slug, + project_id=project_id, + ) + .distinct() + .values( + "name", + "id", + "sequence_id", + "project__identifier", + "project_id", + "workspace__slug", + ) ) def filter_cycles(self, query, slug, project_id): @@ -68,16 +79,20 @@ def filter_cycles(self, query, slug, project_id): q = Q() for field in fields: q |= Q(**{f"{field}__icontains": query}) - return Cycle.objects.filter( - q, - project__project_projectmember__member=self.request.user, - workspace__slug=slug, - project_id=project_id, - ).distinct().values( - "name", - "id", - "project_id", - "workspace__slug", + return ( + Cycle.objects.filter( + q, + project__project_projectmember__member=self.request.user, + workspace__slug=slug, + project_id=project_id, + ) + .distinct() + .values( + "name", + "id", + "project_id", + "workspace__slug", + ) ) def filter_modules(self, query, slug, project_id): @@ -85,16 +100,20 @@ def filter_modules(self, query, slug, project_id): q = Q() for field in fields: q |= Q(**{f"{field}__icontains": query}) - return Module.objects.filter( - q, - project__project_projectmember__member=self.request.user, - workspace__slug=slug, - project_id=project_id, - ).distinct().values( - "name", - "id", - "project_id", - "workspace__slug", + return ( + Module.objects.filter( + q, + project__project_projectmember__member=self.request.user, + workspace__slug=slug, + project_id=project_id, + ) + .distinct() + .values( + "name", + "id", + "project_id", + "workspace__slug", + ) ) def filter_pages(self, query, slug, project_id): @@ -102,16 +121,20 @@ def filter_pages(self, query, slug, project_id): q = Q() for field in fields: q |= Q(**{f"{field}__icontains": query}) - return Page.objects.filter( - q, - project__project_projectmember__member=self.request.user, - workspace__slug=slug, - project_id=project_id, - ).distinct().values( - "name", - "id", - "project_id", - "workspace__slug", + return ( + Page.objects.filter( + q, + project__project_projectmember__member=self.request.user, + workspace__slug=slug, + project_id=project_id, + ) + .distinct() + .values( + "name", + "id", + "project_id", + "workspace__slug", + ) ) def filter_views(self, query, slug, project_id): @@ -119,16 +142,20 @@ def filter_views(self, query, slug, project_id): q = Q() for field in fields: q |= Q(**{f"{field}__icontains": query}) - return IssueView.objects.filter( - q, - project__project_projectmember__member=self.request.user, - workspace__slug=slug, - project_id=project_id, - ).distinct().values( - "name", - "id", - "project_id", - "workspace__slug", + return ( + IssueView.objects.filter( + q, + project__project_projectmember__member=self.request.user, + workspace__slug=slug, + project_id=project_id, + ) + .distinct() + .values( + "name", + "id", + "project_id", + "workspace__slug", + ) ) def get(self, request, slug, project_id): @@ -173,3 +200,35 @@ def get(self, request, slug, project_id): {"error": "Something went wrong please try again later"}, 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) + 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) + + return Response( + issues.values( + "name", + "id", + "sequence_id", + "project__identifier", + "project_id", + "workspace__slug", + ), + status=status.HTTP_200_OK, + ) + + except Exception as e: + capture_exception(e) + return Response({"error": "Something went wrong please try again later"}) diff --git a/apiserver/plane/utils/issue_search.py b/apiserver/plane/utils/issue_search.py new file mode 100644 index 00000000000..93b0df6da3d --- /dev/null +++ b/apiserver/plane/utils/issue_search.py @@ -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() From c656face0a2604f6189265cfeaf4f39e12e7bf0b Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Sat, 1 Apr 2023 10:21:11 +0530 Subject: [PATCH 2/2] dev: update issue search for blocker and blocked by --- apiserver/plane/api/views/search.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/apiserver/plane/api/views/search.py b/apiserver/plane/api/views/search.py index fe5110a8836..70b7d0d0aa6 100644 --- a/apiserver/plane/api/views/search.py +++ b/apiserver/plane/api/views/search.py @@ -207,6 +207,9 @@ 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, @@ -216,6 +219,8 @@ def get(self, request, slug, project_id): 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( @@ -228,7 +233,13 @@ def get(self, request, slug, project_id): ), 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"}) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + )