From 7afe9127637664dd2e1be6eb1e5889b4ee65f609 Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Mon, 11 Dec 2023 17:39:10 +0530 Subject: [PATCH] feat: pages search endpoint --- apiserver/plane/app/urls/search.py | 6 + apiserver/plane/app/views/__init__.py | 2 +- apiserver/plane/app/views/search.py | 193 +++++++++++++++++- .../0051_alter_pagelog_entity_name.py | 18 ++ apiserver/plane/db/models/page.py | 1 + 5 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 apiserver/plane/db/migrations/0051_alter_pagelog_entity_name.py diff --git a/apiserver/plane/app/urls/search.py b/apiserver/plane/app/urls/search.py index 05a79994e..33b49b33e 100644 --- a/apiserver/plane/app/urls/search.py +++ b/apiserver/plane/app/urls/search.py @@ -4,6 +4,7 @@ from django.urls import path from plane.app.views import ( GlobalSearchEndpoint, IssueSearchEndpoint, + SearchEndpoint, ) @@ -18,4 +19,9 @@ urlpatterns = [ IssueSearchEndpoint.as_view(), name="project-issue-search", ), + path( + "workspaces//projects//search/", + SearchEndpoint.as_view(), + name="search", + ), ] diff --git a/apiserver/plane/app/views/__init__.py b/apiserver/plane/app/views/__init__.py index c122dce9f..0efffa659 100644 --- a/apiserver/plane/app/views/__init__.py +++ b/apiserver/plane/app/views/__init__.py @@ -133,7 +133,7 @@ from .page import ( SubPagesEndpoint, ) -from .search import GlobalSearchEndpoint, IssueSearchEndpoint +from .search import GlobalSearchEndpoint, IssueSearchEndpoint, SearchEndpoint from .external import GPTIntegrationEndpoint, ReleaseNotesEndpoint, UnsplashEndpoint diff --git a/apiserver/plane/app/views/search.py b/apiserver/plane/app/views/search.py index ac560643a..251b59637 100644 --- a/apiserver/plane/app/views/search.py +++ b/apiserver/plane/app/views/search.py @@ -10,7 +10,16 @@ from rest_framework.response import Response # Module imports 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, + ProjectMember, +) from plane.utils.issue_search import search_issues @@ -271,3 +280,185 @@ class IssueSearchEndpoint(BaseAPIView): ), status=status.HTTP_200_OK, ) + + +class SearchEndpoint(BaseAPIView): + def get(self, request, slug, project_id): + project = request.query_params.get("project", False) + issue = request.query_params.get("issue", False) + cycle = request.query_params.get("cycle", False) + module = request.query_params.get("module", False) + page = request.query_params.get("page", False) + query = request.query_params.get("query", False) + mention = request.query_params.get("mention", False) + values = int(request.query_params.get("values", 5)) + + if mention: + fields = ["member__first_name", "member__last_name"] + q = Q() + + if query: + for field in fields: + q |= Q(**{f"{field}__icontains": query}) + users = ProjectMember.objects.filter( + q, + project__project_projectmember__member=self.request.user, + project_id=project_id, + workspace__slug=slug, + ).order_by("-created_at").values( + "member__first_name", + "member__last_name", + "member__avatar", + "member__display_name", + "member__id", + )[:values] + + fields = ["name"] + q = Q() + + if query: + for field in fields: + q |= Q(**{f"{field}__icontains": query}) + + pages = Page.objects.filter( + q, + project__project_projectmember__member=self.request.user, + workspace__slug=slug, + access=0, + ).order_by("-created_at").values("name", "id")[:values] + return Response({"users": users, "pages": pages}, status=status.HTTP_200_OK) + + if project: + fields = ["name", "identifier"] + q = Q() + + if query: + for field in fields: + q |= Q(**{f"{field}__icontains": query}) + projects = ( + Project.objects.filter( + q, + Q(project_projectmember__member=self.request.user) | Q(network=2), + workspace__slug=slug, + ) + .order_by("-created_at") + .distinct() + .values("name", "id", "identifier", "workspace__slug")[:values] + ) + return Response(projects, status=status.HTTP_200_OK) + + if issue: + fields = ["name", "sequence_id", "project__identifier"] + q = Q() + + if query: + 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}) + + issues = ( + Issue.issue_objects.filter( + q, + project__project_projectmember__member=self.request.user, + workspace__slug=slug, + ).order_by("-created_at") + .distinct() + .values( + "name", + "id", + "sequence_id", + "project__identifier", + "project_id", + "workspace__slug", + )[:values] + ) + return Response(issues, status=status.HTTP_200_OK) + + if cycle: + fields = ["name"] + q = Q() + + if query: + for field in fields: + q |= Q(**{f"{field}__icontains": query}) + + cycles = ( + Cycle.objects.filter( + q, + project__project_projectmember__member=self.request.user, + workspace__slug=slug, + ) + .order_by("-created_at") + .distinct() + .values( + "name", + "id", + "project_id", + "project__identifier", + "workspace__slug", + )[:values] + ) + return Response(cycles, status=status.HTTP_200_OK) + + if module: + fields = ["name"] + q = Q() + + if query: + for field in fields: + q |= Q(**{f"{field}__icontains": query}) + + modules = ( + Module.objects.filter( + q, + project__project_projectmember__member=self.request.user, + workspace__slug=slug, + ) + .order_by("-created_at") + .distinct() + .values( + "name", + "id", + "project_id", + "project__identifier", + "workspace__slug", + )[:values] + ) + return Response(modules, status=status.HTTP_200_OK) + + if page: + fields = ["name"] + q = Q() + + if query: + for field in fields: + q |= Q(**{f"{field}__icontains": query}) + + pages = ( + Page.objects.filter( + q, + project__project_projectmember__member=self.request.user, + project_id=project_id, + workspace__slug=slug, + access=0, + ) + .order_by("-created_at") + .distinct() + .values( + "name", + "id", + "project_id", + "project__identifier", + "workspace__slug", + )[:values] + ) + return Response(pages, status=status.HTTP_200_OK) + + return Response( + {"error": "please provide a valid query"}, + status=status.HTTP_400_BAD_REQUEST, + ) diff --git a/apiserver/plane/db/migrations/0051_alter_pagelog_entity_name.py b/apiserver/plane/db/migrations/0051_alter_pagelog_entity_name.py new file mode 100644 index 000000000..58f4edce9 --- /dev/null +++ b/apiserver/plane/db/migrations/0051_alter_pagelog_entity_name.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.7 on 2023-12-11 07:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0050_user_use_case_alter_workspace_organization_size'), + ] + + operations = [ + migrations.AlterField( + model_name='pagelog', + name='entity_name', + field=models.CharField(choices=[('to_do', 'To Do'), ('issue', 'issue'), ('image', 'Image'), ('video', 'Video'), ('file', 'File'), ('link', 'Link'), ('cycle', 'Cycle'), ('project', 'Project'), ('module', 'Module'), ('back_link', 'Back Link'), ('forward_link', 'Forward Link'), ('page_mention', 'Page Mention'), ('user_mention', 'User Mention')], max_length=30, verbose_name='Transaction Type'), + ), + ] diff --git a/apiserver/plane/db/models/page.py b/apiserver/plane/db/models/page.py index de65cb98f..e8e3bcb22 100644 --- a/apiserver/plane/db/models/page.py +++ b/apiserver/plane/db/models/page.py @@ -54,6 +54,7 @@ class PageLog(ProjectBaseModel): ("file", "File"), ("link", "Link"), ("cycle","Cycle"), + ("project", "Project"), ("module", "Module"), ("back_link", "Back Link"), ("forward_link", "Forward Link"),