* feat: add project shortcut in command palette * feat: global project switcher shortcut * refactor: generalize command palette entity handling * feat: extend command palette navigation * feat: add issue shortcut to command palette * feat: add modular project selection for cycle navigation * chore: add reusable command palette utilities * fix: update key sequence handling to use window methods for timeout management * fix: build errors * chore: minor ux copy improvements * feat: implement a new command registry and renderer for enhanced command palette functionality * feat: introduce new command palette components and enhance search functionality * feat: enhance command palette components with improved initialization and loading indicators * feat: Implement new command palette architecture with multi-step commands, context-aware filtering, and reusable components. Add comprehensive documentation and integration guides. Enhance command execution with a dedicated executor and context provider. Introduce new command types and improve existing command definitions for better usability and maintainability. * refactor: hook arguments * refactor: folder structure * refactor: update import paths * fix: context prop drilling * refactor: update search components * refactor: create actions * chore: add type to pages * chore: init contextual actions * refactor: context based actions code split * chore: module context-based actions * refactor: streamline command execution flow and enhance multi-step handling in command palette * refactor: remove placeholder management from command execution and implement centralized placeholder mapping * chore: cycle context based actions * refactor: simplify command execution by consolidating selection steps and adding page change handling * chore: added more options to work item contextual actions * chore: page context actions * refactor: update step type definitions and enhance page mapping for command execution * feat: implement Command Palette V2 with global shortcuts and enhanced context handling * refactor: power k v2 * refactor: creation commands * feat: add navigation utility for Power K context handling * feat: implement comprehensive navigation commands for Power K * refactor: work item contextual actions * fix: build errors * refactor: remaining contextual actions * refactor: remove old code * chore: update placeholder * refactor: enhance command registry with observable properties and context-aware shortcut handling * refactor: improve command filtering logic in CommandPaletteModal * chore: context indicator * chore: misc actions * style: shortcut badge * feat: add open entity actions and enhance navigation commands for Power K * refactor: rename and reorganize Power K components for improved clarity and structure * refactor: update CommandPalette components and streamline global shortcuts handling * refactor: adjust debounce timing in CommandPaletteModal for improved responsiveness * feat: implement shortcuts modal and enhance command registry for better shortcut management * fix: search implemented * refactor: search results code split * refactor: search results code split * feat: introduce creation and navigation command modules for Power K, enhancing command organization and functionality * chore: update menu logos * refactor: remove unused PowerKOpenEntityActionsExtended component from command palette * refactor: search menu * fix: clear context on backspace and manual clear * refactor: rename creation command keys for consistency and clarity in Power K * chore: added intake in global search * chore: preferences menu * chore: removed the empty serach params * revert: command palette changes * cleanup * refactor: update command IDs to use underscores for consistency across Power K components * refactor: extended context based actions * chore: modal command item status props * refactor: replace CommandPalette with CommandPaletteProvider in settings and profile layouts * refactor: update settings menu to use translated labels instead of i18n labels * refactor: update command titles to use translation keys for creation actions * refactor: update navigation command titles to use translation keys for consistency * chore: minor cleanup * chore: misc commands added * chore: code split for no search results command * chore: state menu items for work item context based commands * chore: add more props to no search results command * chore: add more props to no search results command * refactor: remove shortcut key for create workspace command * Refactor command palette to use PowerK store - Replaced instances of `useCommandPalette` with `usePowerK` across various components, including `AppSearch`, `CommandModal`, and `CommandPalette`. - Introduced `PowerKStore` to manage modal states and commands, enhancing the command palette functionality. - Updated modal handling to toggle `PowerKModal` and `ShortcutsListModal` instead of the previous command palette modals. - Refactored related components to ensure compatibility with the new store structure and maintain functionality. * Refactor PowerK command handling to remove context dependency - Updated `usePowerKCommands` and `usePowerKCreationCommands` to eliminate the need for a context parameter, simplifying their usage. - Adjusted related command records to utilize the new structure, ensuring consistent access to command configurations. - Enhanced permission checks in creation commands to utilize user project roles for better access control. * chore: add context indicator * chore: update type import * chore: migrate toast implementation from @plane/ui to @plane/propel/toast across multiple command files * refactor: power k modal wrapper and provider * fix: type imports * chore: update creation command shortcuts * fix: page context commands * chore: update navigation and open command shortcuts * fix: work item standalone page modals * fix: context indicator visibility * fix: potential error points * fix: build errors * fix: lint errors * fix: import order --------- Co-authored-by: Vihar Kurama <vihar.kurama@gmail.com> Co-authored-by: Prateek Shourya <prateekshourya29@gmail.com> Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
760 lines
28 KiB
Python
760 lines
28 KiB
Python
# Python imports
|
|
import re
|
|
|
|
# Django imports
|
|
from django.db import models
|
|
from django.db.models import (
|
|
Q,
|
|
OuterRef,
|
|
Subquery,
|
|
Value,
|
|
UUIDField,
|
|
CharField,
|
|
When,
|
|
Case,
|
|
)
|
|
from django.contrib.postgres.aggregates import ArrayAgg
|
|
from django.contrib.postgres.fields import ArrayField
|
|
from django.db.models.functions import Coalesce, Concat
|
|
from django.utils import timezone
|
|
|
|
# Third party imports
|
|
from rest_framework import status
|
|
from rest_framework.response import Response
|
|
|
|
# Module imports
|
|
from plane.app.views.base import BaseAPIView
|
|
from plane.db.models import (
|
|
Workspace,
|
|
Project,
|
|
Issue,
|
|
Cycle,
|
|
Module,
|
|
Page,
|
|
IssueView,
|
|
ProjectMember,
|
|
ProjectPage,
|
|
WorkspaceMember,
|
|
)
|
|
|
|
|
|
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, _workspace_search):
|
|
fields = ["name"]
|
|
q = Q()
|
|
if query:
|
|
for field in fields:
|
|
q |= Q(**{f"{field}__icontains": query})
|
|
return (
|
|
Workspace.objects.filter(q, workspace_member__member=self.request.user)
|
|
.order_by("-created_at")
|
|
.distinct()
|
|
.values("name", "id", "slug")
|
|
)
|
|
|
|
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})
|
|
return (
|
|
Project.objects.filter(
|
|
q,
|
|
project_projectmember__member=self.request.user,
|
|
project_projectmember__is_active=True,
|
|
archived_at__isnull=True,
|
|
workspace__slug=slug,
|
|
)
|
|
.order_by("-created_at")
|
|
.distinct()
|
|
.values("name", "id", "identifier", "workspace__slug")
|
|
)
|
|
|
|
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})
|
|
|
|
issues = Issue.issue_objects.filter(
|
|
q,
|
|
project__project_projectmember__member=self.request.user,
|
|
project__project_projectmember__is_active=True,
|
|
project__archived_at__isnull=True,
|
|
workspace__slug=slug,
|
|
)
|
|
|
|
if workspace_search == "false" and project_id:
|
|
issues = issues.filter(project_id=project_id)
|
|
|
|
return issues.distinct().values(
|
|
"name",
|
|
"id",
|
|
"sequence_id",
|
|
"project__identifier",
|
|
"project_id",
|
|
"workspace__slug",
|
|
)[: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})
|
|
|
|
cycles = Cycle.objects.filter(
|
|
q,
|
|
project__project_projectmember__member=self.request.user,
|
|
project__project_projectmember__is_active=True,
|
|
project__archived_at__isnull=True,
|
|
workspace__slug=slug,
|
|
)
|
|
|
|
if workspace_search == "false" and project_id:
|
|
cycles = cycles.filter(project_id=project_id)
|
|
|
|
return (
|
|
cycles.order_by("-created_at")
|
|
.distinct()
|
|
.values(
|
|
"name", "id", "project_id", "project__identifier", "workspace__slug"
|
|
)
|
|
)
|
|
|
|
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})
|
|
|
|
modules = Module.objects.filter(
|
|
q,
|
|
project__project_projectmember__member=self.request.user,
|
|
project__project_projectmember__is_active=True,
|
|
project__archived_at__isnull=True,
|
|
workspace__slug=slug,
|
|
)
|
|
|
|
if workspace_search == "false" and project_id:
|
|
modules = modules.filter(project_id=project_id)
|
|
|
|
return (
|
|
modules.order_by("-created_at")
|
|
.distinct()
|
|
.values(
|
|
"name", "id", "project_id", "project__identifier", "workspace__slug"
|
|
)
|
|
)
|
|
|
|
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})
|
|
|
|
pages = (
|
|
Page.objects.filter(
|
|
q,
|
|
projects__project_projectmember__member=self.request.user,
|
|
projects__project_projectmember__is_active=True,
|
|
projects__archived_at__isnull=True,
|
|
workspace__slug=slug,
|
|
)
|
|
.annotate(
|
|
project_ids=Coalesce(
|
|
ArrayAgg(
|
|
"projects__id", distinct=True, filter=~Q(projects__id=True)
|
|
),
|
|
Value([], output_field=ArrayField(UUIDField())),
|
|
)
|
|
)
|
|
.annotate(
|
|
project_identifiers=Coalesce(
|
|
ArrayAgg(
|
|
"projects__identifier",
|
|
distinct=True,
|
|
filter=~Q(projects__id=True),
|
|
),
|
|
Value([], output_field=ArrayField(CharField())),
|
|
)
|
|
)
|
|
)
|
|
|
|
if workspace_search == "false" and project_id:
|
|
project_subquery = ProjectPage.objects.filter(
|
|
page_id=OuterRef("id"), project_id=project_id
|
|
).values_list("project_id", flat=True)[:1]
|
|
|
|
pages = pages.annotate(project_id=Subquery(project_subquery)).filter(
|
|
project_id=project_id
|
|
)
|
|
|
|
return (
|
|
pages.order_by("-created_at")
|
|
.distinct()
|
|
.values(
|
|
"name", "id", "project_ids", "project_identifiers", "workspace__slug"
|
|
)
|
|
)
|
|
|
|
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})
|
|
|
|
issue_views = IssueView.objects.filter(
|
|
q,
|
|
project__project_projectmember__member=self.request.user,
|
|
project__project_projectmember__is_active=True,
|
|
project__archived_at__isnull=True,
|
|
workspace__slug=slug,
|
|
)
|
|
|
|
if workspace_search == "false" and project_id:
|
|
issue_views = issue_views.filter(project_id=project_id)
|
|
|
|
return (
|
|
issue_views.order_by("-created_at")
|
|
.distinct()
|
|
.values(
|
|
"name", "id", "project_id", "project__identifier", "workspace__slug"
|
|
)
|
|
)
|
|
|
|
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})
|
|
|
|
issues = Issue.objects.filter(
|
|
q,
|
|
project__project_projectmember__member=self.request.user,
|
|
project__project_projectmember__is_active=True,
|
|
project__archived_at__isnull=True,
|
|
workspace__slug=slug,
|
|
).filter(models.Q(issue_intake__status=0) | models.Q(issue_intake__status=-2))
|
|
|
|
if workspace_search == "false" and project_id:
|
|
issues = issues.filter(project_id=project_id)
|
|
|
|
return (
|
|
issues.order_by("-created_at")
|
|
.distinct()
|
|
.values(
|
|
"name",
|
|
"id",
|
|
"sequence_id",
|
|
"project__identifier",
|
|
"project_id",
|
|
"workspace__slug",
|
|
)[:100]
|
|
)
|
|
|
|
def get(self, request, slug):
|
|
query = request.query_params.get("search", False)
|
|
entities_param = request.query_params.get("entities")
|
|
workspace_search = request.query_params.get("workspace_search", "false")
|
|
project_id = request.query_params.get("project_id", False)
|
|
|
|
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,
|
|
"intake": self.filter_intakes,
|
|
}
|
|
|
|
# Determine which entities to search
|
|
if entities_param:
|
|
requested_entities = [
|
|
e.strip() for e in entities_param.split(",") if e.strip()
|
|
]
|
|
requested_entities = [e for e in requested_entities if e in MODELS_MAPPER]
|
|
else:
|
|
requested_entities = list(MODELS_MAPPER.keys())
|
|
|
|
results = {}
|
|
|
|
for entity in requested_entities:
|
|
func = MODELS_MAPPER.get(entity)
|
|
if func:
|
|
results[entity] = func(
|
|
query or None, slug, project_id, workspace_search
|
|
)
|
|
|
|
return Response({"results": results}, status=status.HTTP_200_OK)
|
|
|
|
|
|
class SearchEndpoint(BaseAPIView):
|
|
def get(self, request, slug):
|
|
query = request.query_params.get("query", False)
|
|
query_types = request.query_params.get("query_type", "user_mention").split(",")
|
|
query_types = [qt.strip() for qt in query_types]
|
|
count = int(request.query_params.get("count", 5))
|
|
project_id = request.query_params.get("project_id", None)
|
|
issue_id = request.query_params.get("issue_id", None)
|
|
|
|
response_data = {}
|
|
|
|
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})
|
|
|
|
users = (
|
|
ProjectMember.objects.filter(
|
|
q,
|
|
is_active=True,
|
|
workspace__slug=slug,
|
|
member__is_bot=False,
|
|
project_id=project_id,
|
|
)
|
|
.annotate(
|
|
member__avatar_url=Case(
|
|
When(
|
|
member__avatar_asset__isnull=False,
|
|
then=Concat(
|
|
Value("/api/assets/v2/static/"),
|
|
"member__avatar_asset",
|
|
Value("/"),
|
|
),
|
|
),
|
|
When(
|
|
member__avatar_asset__isnull=True,
|
|
then="member__avatar",
|
|
),
|
|
default=Value(None),
|
|
output_field=CharField(),
|
|
)
|
|
)
|
|
.order_by("-created_at")
|
|
)
|
|
|
|
users = (
|
|
users
|
|
.distinct()
|
|
.values(
|
|
"member__avatar_url",
|
|
"member__display_name",
|
|
"member__id",
|
|
)
|
|
)
|
|
|
|
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})
|
|
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", "logo_props", "workspace__slug"
|
|
)[:count]
|
|
)
|
|
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})
|
|
|
|
issues = (
|
|
Issue.issue_objects.filter(
|
|
q,
|
|
project__project_projectmember__member=self.request.user,
|
|
project__project_projectmember__is_active=True,
|
|
workspace__slug=slug,
|
|
project_id=project_id,
|
|
)
|
|
.order_by("-created_at")
|
|
.distinct()
|
|
.values(
|
|
"name",
|
|
"id",
|
|
"sequence_id",
|
|
"project__identifier",
|
|
"project_id",
|
|
"priority",
|
|
"state_id",
|
|
"type_id",
|
|
)[:count]
|
|
)
|
|
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})
|
|
|
|
cycles = (
|
|
Cycle.objects.filter(
|
|
q,
|
|
project__project_projectmember__member=self.request.user,
|
|
project__project_projectmember__is_active=True,
|
|
workspace__slug=slug,
|
|
project_id=project_id,
|
|
)
|
|
.annotate(
|
|
status=Case(
|
|
When(
|
|
Q(start_date__lte=timezone.now())
|
|
& Q(end_date__gte=timezone.now()),
|
|
then=Value("CURRENT"),
|
|
),
|
|
When(
|
|
start_date__gt=timezone.now(),
|
|
then=Value("UPCOMING"),
|
|
),
|
|
When(
|
|
end_date__lt=timezone.now(), then=Value("COMPLETED")
|
|
),
|
|
When(
|
|
Q(start_date__isnull=True)
|
|
& Q(end_date__isnull=True),
|
|
then=Value("DRAFT"),
|
|
),
|
|
default=Value("DRAFT"),
|
|
output_field=CharField(),
|
|
)
|
|
)
|
|
.order_by("-created_at")
|
|
.distinct()
|
|
.values(
|
|
"name",
|
|
"id",
|
|
"project_id",
|
|
"project__identifier",
|
|
"status",
|
|
"workspace__slug",
|
|
)[:count]
|
|
)
|
|
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})
|
|
|
|
modules = (
|
|
Module.objects.filter(
|
|
q,
|
|
project__project_projectmember__member=self.request.user,
|
|
project__project_projectmember__is_active=True,
|
|
workspace__slug=slug,
|
|
project_id=project_id,
|
|
)
|
|
.order_by("-created_at")
|
|
.distinct()
|
|
.values(
|
|
"name",
|
|
"id",
|
|
"project_id",
|
|
"project__identifier",
|
|
"status",
|
|
"workspace__slug",
|
|
)[:count]
|
|
)
|
|
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})
|
|
|
|
pages = (
|
|
Page.objects.filter(
|
|
q,
|
|
projects__project_projectmember__member=self.request.user,
|
|
projects__project_projectmember__is_active=True,
|
|
projects__id=project_id,
|
|
workspace__slug=slug,
|
|
access=0,
|
|
)
|
|
.order_by("-created_at")
|
|
.distinct()
|
|
.values(
|
|
"name",
|
|
"id",
|
|
"logo_props",
|
|
"projects__id",
|
|
"workspace__slug",
|
|
)[:count]
|
|
)
|
|
response_data["page"] = list(pages)
|
|
return Response(response_data, status=status.HTTP_200_OK)
|
|
|
|
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})
|
|
users = (
|
|
WorkspaceMember.objects.filter(
|
|
q,
|
|
is_active=True,
|
|
workspace__slug=slug,
|
|
member__is_bot=False,
|
|
)
|
|
.annotate(
|
|
member__avatar_url=Case(
|
|
When(
|
|
member__avatar_asset__isnull=False,
|
|
then=Concat(
|
|
Value("/api/assets/v2/static/"),
|
|
"member__avatar_asset",
|
|
Value("/"),
|
|
),
|
|
),
|
|
When(
|
|
member__avatar_asset__isnull=True,
|
|
then="member__avatar",
|
|
),
|
|
default=Value(None),
|
|
output_field=models.CharField(),
|
|
)
|
|
)
|
|
.order_by("-created_at")
|
|
.values(
|
|
"member__avatar_url", "member__display_name", "member__id"
|
|
)[:count]
|
|
)
|
|
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})
|
|
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", "logo_props", "workspace__slug"
|
|
)[:count]
|
|
)
|
|
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})
|
|
|
|
issues = (
|
|
Issue.issue_objects.filter(
|
|
q,
|
|
project__project_projectmember__member=self.request.user,
|
|
project__project_projectmember__is_active=True,
|
|
workspace__slug=slug,
|
|
)
|
|
.order_by("-created_at")
|
|
.distinct()
|
|
.values(
|
|
"name",
|
|
"id",
|
|
"sequence_id",
|
|
"project__identifier",
|
|
"project_id",
|
|
"priority",
|
|
"state_id",
|
|
"type_id",
|
|
)[:count]
|
|
)
|
|
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})
|
|
|
|
cycles = (
|
|
Cycle.objects.filter(
|
|
q,
|
|
project__project_projectmember__member=self.request.user,
|
|
project__project_projectmember__is_active=True,
|
|
workspace__slug=slug,
|
|
)
|
|
.annotate(
|
|
status=Case(
|
|
When(
|
|
Q(start_date__lte=timezone.now())
|
|
& Q(end_date__gte=timezone.now()),
|
|
then=Value("CURRENT"),
|
|
),
|
|
When(
|
|
start_date__gt=timezone.now(),
|
|
then=Value("UPCOMING"),
|
|
),
|
|
When(
|
|
end_date__lt=timezone.now(), then=Value("COMPLETED")
|
|
),
|
|
When(
|
|
Q(start_date__isnull=True)
|
|
& Q(end_date__isnull=True),
|
|
then=Value("DRAFT"),
|
|
),
|
|
default=Value("DRAFT"),
|
|
output_field=CharField(),
|
|
)
|
|
)
|
|
.order_by("-created_at")
|
|
.distinct()
|
|
.values(
|
|
"name",
|
|
"id",
|
|
"project_id",
|
|
"project__identifier",
|
|
"status",
|
|
"workspace__slug",
|
|
)[:count]
|
|
)
|
|
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})
|
|
|
|
modules = (
|
|
Module.objects.filter(
|
|
q,
|
|
project__project_projectmember__member=self.request.user,
|
|
project__project_projectmember__is_active=True,
|
|
workspace__slug=slug,
|
|
)
|
|
.order_by("-created_at")
|
|
.distinct()
|
|
.values(
|
|
"name",
|
|
"id",
|
|
"project_id",
|
|
"project__identifier",
|
|
"status",
|
|
"workspace__slug",
|
|
)[:count]
|
|
)
|
|
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})
|
|
|
|
pages = (
|
|
Page.objects.filter(
|
|
q,
|
|
projects__project_projectmember__member=self.request.user,
|
|
projects__project_projectmember__is_active=True,
|
|
workspace__slug=slug,
|
|
access=0,
|
|
is_global=True,
|
|
)
|
|
.order_by("-created_at")
|
|
.distinct()
|
|
.values(
|
|
"name",
|
|
"id",
|
|
"logo_props",
|
|
"projects__id",
|
|
"workspace__slug",
|
|
)[:count]
|
|
)
|
|
response_data["page"] = list(pages)
|
|
return Response(response_data, status=status.HTTP_200_OK)
|