Skip to content
Open
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
193 changes: 48 additions & 145 deletions apps/api/plane/app/views/search/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
# SPDX-License-Identifier: AGPL-3.0-only
# See the LICENSE file for details.

# Python imports
import re

# Django imports
from django.db import models
from django.db.models import (
Expand All @@ -29,6 +26,18 @@
# Module imports
from plane.app.views.base import BaseAPIView
from plane.app.permissions import WorkspaceUserPermission
from plane.utils.search import (
CYCLE_SEARCH_FIELDS,
ISSUE_SEARCH_FIELDS,
ISSUE_SEQUENCE_FIELDS,
MODULE_SEARCH_FIELDS,
PAGE_SEARCH_FIELDS,
PROJECT_SEARCH_FIELDS,
USER_MENTION_SEARCH_FIELDS,
VIEW_SEARCH_FIELDS,
WORKSPACE_SEARCH_FIELDS,
build_search_query,
)
from plane.db.models import (
Workspace,
Project,
Expand All @@ -49,11 +58,7 @@ class GlobalSearchEndpoint(BaseAPIView):
"""

def filter_workspaces(self, query, _slug, _project_id, _workspace_search):
fields = ["name"]
q = Q()
if query:
for field in fields:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(query, fields=WORKSPACE_SEARCH_FIELDS)
return (
Workspace.objects.filter(q, workspace_member__member=self.request.user)
.order_by("-created_at")
Expand All @@ -62,11 +67,7 @@ def filter_workspaces(self, query, _slug, _project_id, _workspace_search):
)

def filter_projects(self, query, slug, _project_id, _workspace_search):
fields = ["name", "identifier"]
q = Q()
if query:
for field in fields:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(query, fields=PROJECT_SEARCH_FIELDS)
return (
Project.objects.filter(
q,
Expand All @@ -81,17 +82,11 @@ def filter_projects(self, query, slug, _project_id, _workspace_search):
)

def filter_issues(self, query, slug, project_id, workspace_search):
fields = ["name", "sequence_id", "project__identifier"]
q = Q()
if query:
for field in fields:
if field == "sequence_id":
# Match whole integers only (exclude decimal numbers)
sequences = re.findall(r"\b\d+\b", query)
for sequence_id in sequences:
q |= Q(**{"sequence_id": sequence_id})
else:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(
query,
fields=ISSUE_SEARCH_FIELDS,
sequence_fields=ISSUE_SEQUENCE_FIELDS,
)

issues = Issue.issue_objects.filter(
q,
Expand All @@ -114,11 +109,7 @@ def filter_issues(self, query, slug, project_id, workspace_search):
)[:100]

def filter_cycles(self, query, slug, project_id, workspace_search):
fields = ["name"]
q = Q()
if query:
for field in fields:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(query, fields=CYCLE_SEARCH_FIELDS)

cycles = Cycle.objects.filter(
q,
Expand All @@ -138,11 +129,7 @@ def filter_cycles(self, query, slug, project_id, workspace_search):
)

def filter_modules(self, query, slug, project_id, workspace_search):
fields = ["name"]
q = Q()
if query:
for field in fields:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(query, fields=MODULE_SEARCH_FIELDS)

modules = Module.objects.filter(
q,
Expand All @@ -162,11 +149,7 @@ def filter_modules(self, query, slug, project_id, workspace_search):
)

def filter_pages(self, query, slug, project_id, workspace_search):
fields = ["name"]
q = Q()
if query:
for field in fields:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(query, fields=PAGE_SEARCH_FIELDS)

pages = (
Page.objects.filter(
Expand Down Expand Up @@ -208,11 +191,7 @@ def filter_pages(self, query, slug, project_id, workspace_search):
)

def filter_views(self, query, slug, project_id, workspace_search):
fields = ["name"]
q = Q()
if query:
for field in fields:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(query, fields=VIEW_SEARCH_FIELDS)

issue_views = IssueView.objects.filter(
q,
Expand All @@ -232,17 +211,11 @@ def filter_views(self, query, slug, project_id, workspace_search):
)

def filter_intakes(self, query, slug, project_id, workspace_search):
fields = ["name", "sequence_id", "project__identifier"]
q = Q()
if query:
for field in fields:
if field == "sequence_id":
# Match whole integers only (exclude decimal numbers)
sequences = re.findall(r"\b\d+\b", query)
for sequence_id in sequences:
q |= Q(**{"sequence_id": sequence_id})
else:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(
query,
fields=ISSUE_SEARCH_FIELDS,
sequence_fields=ISSUE_SEQUENCE_FIELDS,
)

issues = Issue.objects.filter(
q,
Expand Down Expand Up @@ -317,16 +290,7 @@ def get(self, request, slug):
if project_id:
for query_type in query_types:
if query_type == "user_mention":
fields = [
"member__first_name",
"member__last_name",
"member__display_name",
]
q = Q()

if query:
for field in fields:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(query, fields=USER_MENTION_SEARCH_FIELDS)

users = (
ProjectMember.objects.filter(
Expand Down Expand Up @@ -366,12 +330,7 @@ def get(self, request, slug):
response_data["user_mention"] = list(users[:count])

elif query_type == "project":
fields = ["name", "identifier"]
q = Q()

if query:
for field in fields:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(query, fields=PROJECT_SEARCH_FIELDS)
projects = (
Project.objects.filter(
q,
Expand All @@ -385,17 +344,11 @@ def get(self, request, slug):
response_data["project"] = list(projects)

elif query_type == "issue":
fields = ["name", "sequence_id", "project__identifier"]
q = Q()

if query:
for field in fields:
if field == "sequence_id":
sequences = re.findall(r"\b\d+\b", query)
for sequence_id in sequences:
q |= Q(**{"sequence_id": sequence_id})
else:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(
query,
fields=ISSUE_SEARCH_FIELDS,
sequence_fields=ISSUE_SEQUENCE_FIELDS,
)

issues = (
Issue.issue_objects.filter(
Expand All @@ -421,12 +374,7 @@ def get(self, request, slug):
response_data["issue"] = list(issues)

elif query_type == "cycle":
fields = ["name"]
q = Q()

if query:
for field in fields:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(query, fields=CYCLE_SEARCH_FIELDS)

cycles = (
Cycle.objects.filter(
Expand Down Expand Up @@ -469,12 +417,7 @@ def get(self, request, slug):
response_data["cycle"] = list(cycles)

elif query_type == "module":
fields = ["name"]
q = Q()

if query:
for field in fields:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(query, fields=MODULE_SEARCH_FIELDS)

modules = (
Module.objects.filter(
Expand All @@ -498,12 +441,7 @@ def get(self, request, slug):
response_data["module"] = list(modules)

elif query_type == "page":
fields = ["name"]
q = Q()

if query:
for field in fields:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(query, fields=PAGE_SEARCH_FIELDS)

pages = (
Page.objects.filter(
Expand All @@ -530,16 +468,7 @@ def get(self, request, slug):
else:
for query_type in query_types:
if query_type == "user_mention":
fields = [
"member__first_name",
"member__last_name",
"member__display_name",
]
q = Q()

if query:
for field in fields:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(query, fields=USER_MENTION_SEARCH_FIELDS)
users = (
WorkspaceMember.objects.filter(
q,
Expand Down Expand Up @@ -571,12 +500,7 @@ def get(self, request, slug):
response_data["user_mention"] = list(users)

elif query_type == "project":
fields = ["name", "identifier"]
q = Q()

if query:
for field in fields:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(query, fields=PROJECT_SEARCH_FIELDS)
projects = (
Project.objects.filter(
q,
Expand All @@ -590,17 +514,11 @@ def get(self, request, slug):
response_data["project"] = list(projects)

elif query_type == "issue":
fields = ["name", "sequence_id", "project__identifier"]
q = Q()

if query:
for field in fields:
if field == "sequence_id":
sequences = re.findall(r"\b\d+\b", query)
for sequence_id in sequences:
q |= Q(**{"sequence_id": sequence_id})
else:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(
query,
fields=ISSUE_SEARCH_FIELDS,
sequence_fields=ISSUE_SEQUENCE_FIELDS,
)

issues = (
Issue.issue_objects.filter(
Expand All @@ -625,12 +543,7 @@ def get(self, request, slug):
response_data["issue"] = list(issues)

elif query_type == "cycle":
fields = ["name"]
q = Q()

if query:
for field in fields:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(query, fields=CYCLE_SEARCH_FIELDS)

cycles = (
Cycle.objects.filter(
Expand Down Expand Up @@ -672,12 +585,7 @@ def get(self, request, slug):
response_data["cycle"] = list(cycles)

elif query_type == "module":
fields = ["name"]
q = Q()

if query:
for field in fields:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(query, fields=MODULE_SEARCH_FIELDS)

modules = (
Module.objects.filter(
Expand All @@ -700,12 +608,7 @@ def get(self, request, slug):
response_data["module"] = list(modules)

elif query_type == "page":
fields = ["name"]
q = Q()

if query:
for field in fields:
q |= Q(**{f"{field}__icontains": query})
q = build_search_query(query, fields=PAGE_SEARCH_FIELDS)

pages = (
Page.objects.filter(
Expand Down
4 changes: 1 addition & 3 deletions apps/api/plane/tests/contract/api/test_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ def test_active_user_can_access_with_api_key(self, api_key_client):
assert response.status_code == status.HTTP_200_OK

@pytest.mark.django_db
def test_deactivated_user_cannot_access_with_api_key(
self, api_key_client, create_user
):
def test_deactivated_user_cannot_access_with_api_key(self, api_key_client, create_user):
# The account is disabled after the API key was generated.
create_user.is_active = False
create_user.save()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ def attacker_membership(db, workspace, create_user):
workspace=workspace,
created_by=create_user,
)
ProjectMember.objects.create(
project=other, member=create_user, workspace=workspace, role=20
)
ProjectMember.objects.create(project=other, member=create_user, workspace=workspace, role=20)
return other


Expand All @@ -62,9 +60,7 @@ def foreign_project(db, workspace):
workspace=workspace,
created_by=owner,
)
ProjectMember.objects.create(
project=project, member=owner, workspace=workspace, role=20
)
ProjectMember.objects.create(project=project, member=owner, workspace=workspace, role=20)
return project


Expand All @@ -90,9 +86,7 @@ def test_project_member_can_list_roster(self, api_key_client, workspace, create_
workspace=workspace,
created_by=create_user,
)
ProjectMember.objects.create(
project=project, member=create_user, workspace=workspace, role=20
)
ProjectMember.objects.create(project=project, member=create_user, workspace=workspace, role=20)
response = api_key_client.get(members_url(workspace.slug, project.id))
assert response.status_code == status.HTTP_200_OK, (
f"Got {response.status_code}: {getattr(response, 'data', None)!r}"
Expand Down
Loading