* dev: initialize inbox * dev: inbox and inbox issues models, views and serializers * dev: issue object filter for inbox * dev: filter for search issues * dev: inbox snooze and duplicates * dev: set duplicate to null by default * feat: inbox ui and services * feat: project detail in inbox * style: layout, popover, icons, sidebar * dev: default inbox for project and pending issues count * dev: fix exception when creating default inbox * fix: empty state for inbox * dev: auto issue state updation when rejected or marked duplicate * fix: inbox update status * fix: hydrating chose with old values filters workflow * feat: inbox issue filtering * fix: issue inbox filtering * feat: filter inbox issues * refactor: analytics, border colors * dev: filters and views for inbox * dev: source for inboxissue and update list inbox issue * dev: update list endpoint to house filters and additional data * dev: bridge id for list * dev: remove print logs * dev: update inbox issue workflow * dev: add description_html in issue details * fix: inbox track event auth, chore: inbox issue action authorization * fix: removed unnecessary api calls * style: viewed issues * fix: priority validation * dev: remove print logs * dev: update issue inbox update workflow * chore: added inbox view context * fix: type errors * fix: build errors and warnings * dev: update issue inbox workflow and log all the changes * fix: filters logic, sidebar fields to show * dev: update issue filtering status * chore: update create inbox issue modal, fix: mutation issues * dev: update issue accept workflow * chore: add comment to inbox issues * chore: remove inboxIssueId from url after deleting * dev: update the issue triage workflow * fix: mutation after issue status change * chore: issue details sidebar divider * fix: issue activity for inbox issues * dev: update inbox perrmissions * dev: create new permission layer * chore: auth layer for inbox * chore: show accepting status * chore: show issue status at the top of issue details --------- Co-authored-by: Dakshesh Jain <dakshesh.jain14@gmail.com> Co-authored-by: gurusainath <gurusainath007@gmail.com> Co-authored-by: Aaryan Khandelwal <aaryankhandu123@gmail.com>
259 lines
8.2 KiB
Python
259 lines
8.2 KiB
Python
# Python imports
|
|
import re
|
|
|
|
# Django imports
|
|
from django.db.models import Q
|
|
|
|
# Third party imports
|
|
from rest_framework import status
|
|
from rest_framework.response import Response
|
|
from sentry_sdk import capture_exception
|
|
|
|
# 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):
|
|
"""Endpoint to search across multiple fields in the workspace and
|
|
also show related workspace if found
|
|
"""
|
|
|
|
def filter_workspaces(self, query, slug, project_id):
|
|
fields = ["name"]
|
|
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")
|
|
)
|
|
|
|
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")
|
|
)
|
|
|
|
def filter_issues(self, query, slug, project_id):
|
|
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.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):
|
|
fields = ["name"]
|
|
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",
|
|
)
|
|
)
|
|
|
|
def filter_modules(self, query, slug, project_id):
|
|
fields = ["name"]
|
|
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",
|
|
)
|
|
)
|
|
|
|
def filter_pages(self, query, slug, project_id):
|
|
fields = ["name"]
|
|
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",
|
|
)
|
|
)
|
|
|
|
def filter_views(self, query, slug, project_id):
|
|
fields = ["name"]
|
|
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",
|
|
)
|
|
)
|
|
|
|
def get(self, request, slug, project_id):
|
|
try:
|
|
query = request.query_params.get("search", False)
|
|
if not query:
|
|
return Response(
|
|
{
|
|
"results": {
|
|
"workspace": [],
|
|
"project": [],
|
|
"issue": [],
|
|
"cycle": [],
|
|
"module": [],
|
|
"issue_view": [],
|
|
"page": [],
|
|
}
|
|
},
|
|
status=status.HTTP_200_OK,
|
|
)
|
|
|
|
MODELS_MAPPER = {
|
|
"workspace": self.filter_workspaces,
|
|
"project": self.filter_projects,
|
|
"issue": self.filter_issues,
|
|
"cycle": self.filter_cycles,
|
|
"module": self.filter_modules,
|
|
"issue_view": self.filter_views,
|
|
"page": self.filter_pages,
|
|
}
|
|
|
|
results = {}
|
|
|
|
for model in MODELS_MAPPER.keys():
|
|
func = MODELS_MAPPER.get(model, None)
|
|
results[model] = func(query, slug, project_id)
|
|
return Response({"results": results}, 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,
|
|
)
|
|
|
|
|
|
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 = Issue.issue_objects.filter(
|
|
workspace__slug=slug,
|
|
project_id=project_id,
|
|
project__project_projectmember__member=self.request.user,
|
|
)
|
|
|
|
if query:
|
|
issues = search_issues(query, issues)
|
|
|
|
if parent == "true" and issue_id:
|
|
issue = Issue.issue_objects.get(pk=issue_id)
|
|
issues = issues.filter(
|
|
~Q(pk=issue_id), ~Q(pk=issue.parent_id), parent__isnull=True
|
|
).exclude(
|
|
pk__in=Issue.issue_objects.filter(parent__isnull=False).values_list(
|
|
"parent_id", flat=True
|
|
)
|
|
)
|
|
if blocker_blocked_by == "true" and issue_id:
|
|
issue = Issue.issue_objects.get(pk=issue_id)
|
|
issues = issues.filter(
|
|
~Q(pk=issue_id),
|
|
~Q(blocked_issues__block=issue),
|
|
~Q(blocker_issues__blocked_by=issue),
|
|
)
|
|
|
|
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,
|
|
)
|