diff --git a/apps/api/plane/graphql/helpers/intake.py b/apps/api/plane/graphql/helpers/intake.py index 46410dfdf5..b2b9720c32 100644 --- a/apps/api/plane/graphql/helpers/intake.py +++ b/apps/api/plane/graphql/helpers/intake.py @@ -1,4 +1,5 @@ # Python imports +import re from typing import Optional # Third Party Imports @@ -212,6 +213,7 @@ def get_intake_work_items( filters: Optional[JSON] = {}, orderBy: Optional[str] = "-created_at", is_snoozed_work_items_required: Optional[bool] = False, + search: Optional[str] = None, ): base_query = intake_work_item_base_query( workspace_slug=workspace_slug, project_id=project_id, user_id=user_id @@ -226,6 +228,18 @@ def get_intake_work_items( | Q(snoozed_till__isnull=True) ) + if search: + q = Q() + fields = ["issue__name", "issue__sequence_id", "issue__project__identifier"] + for field in fields: + if field == "issue__sequence_id": + sequences = re.findall(r"\b\d+\b", search) + for sequence_id in sequences: + q |= Q(**{"issue__sequence_id": sequence_id}) + else: + q |= Q(**{f"{field}__icontains": search}) + base_query = base_query.filter(q) + intakes = ( base_query.filter(**filters) .select_related("workspace", "project", "issue") @@ -244,6 +258,7 @@ def get_intake_work_items_async( filters: Optional[JSON] = {}, orderBy: Optional[str] = "-created_at", is_snoozed_work_items_required: Optional[bool] = False, + search: Optional[str] = None, ): return get_intake_work_items( workspace_slug=workspace_slug, @@ -252,6 +267,7 @@ def get_intake_work_items_async( filters=filters, orderBy=orderBy, is_snoozed_work_items_required=is_snoozed_work_items_required, + search=search, ) diff --git a/apps/api/plane/graphql/queries/intake/__init__.py b/apps/api/plane/graphql/queries/intake/__init__.py index b0d4b45e12..663414a993 100644 --- a/apps/api/plane/graphql/queries/intake/__init__.py +++ b/apps/api/plane/graphql/queries/intake/__init__.py @@ -3,3 +3,4 @@ from .attachment import IntakeWorkItemAttachmentQuery from .base import IntakeCountQuery, IntakeWorkItemQuery from .comment import IntakeWorkItemCommentQuery from .comment_reaction import IntakeWorkItemCommentReactionQuery +from .search import IntakeSearchQuery diff --git a/apps/api/plane/graphql/queries/intake/base.py b/apps/api/plane/graphql/queries/intake/base.py index ca0d9a6c04..19ccb12eed 100644 --- a/apps/api/plane/graphql/queries/intake/base.py +++ b/apps/api/plane/graphql/queries/intake/base.py @@ -61,9 +61,26 @@ class IntakeCountQuery: workspace_slug=workspace_slug, project_id=project_id ) + current_user_role = None project_member = await get_project_member( - workspace_slug=workspace_slug, project_id=project_id, user_id=user_id + workspace_slug=workspace_slug, + project_id=project_id, + user_id=user_id, + raise_exception=False, ) + if not project_member: + project_teamspace_filter = await project_member_filter_via_teamspaces_async( + user_id=user_id, + workspace_slug=workspace_slug, + ) + teamspace_project_ids = project_teamspace_filter.teamspace_project_ids + if project_id not in teamspace_project_ids: + message = "You are not allowed to access this project" + error_extensions = {"code": "FORBIDDEN", "statusCode": 403} + raise GraphQLError(message, extensions=error_extensions) + current_user_role = Roles.MEMBER.value + else: + current_user_role = project_member.role filters = intake_filters(filters) @@ -83,7 +100,7 @@ class IntakeCountQuery: is_snoozed_work_items_required = True intake_work_items = await get_intake_work_items_async( - user_id=user_id if project_member.role == Roles.GUEST.value else None, + user_id=user_id if current_user_role == Roles.GUEST.value else None, workspace_slug=workspace_slug, project_id=project_id, filters=filters, diff --git a/apps/api/plane/graphql/queries/intake/search.py b/apps/api/plane/graphql/queries/intake/search.py new file mode 100644 index 0000000000..aaf37c2e5d --- /dev/null +++ b/apps/api/plane/graphql/queries/intake/search.py @@ -0,0 +1,90 @@ +# Python imports +from typing import Optional + +# Third-Party Imports +import strawberry + +# Strawberry Imports +from strawberry.exceptions import GraphQLError +from strawberry.permission import PermissionExtension +from strawberry.types import Info + +# Module Imports +from plane.graphql.helpers import ( + get_intake_work_items_async, + get_project, + get_project_member, + get_workspace, + is_project_intakes_enabled_async, +) +from plane.graphql.helpers.teamspace import project_member_filter_via_teamspaces_async +from plane.graphql.permissions.workspace import WorkspaceBasePermission +from plane.graphql.types.intake.base import IntakeWorkItemType +from plane.graphql.types.paginator import PaginatorResponse +from plane.graphql.utils.paginator import paginate +from plane.graphql.utils.roles import Roles + + +@strawberry.type +class IntakeSearchQuery: + @strawberry.field( + extensions=[PermissionExtension(permissions=[WorkspaceBasePermission()])] + ) + + # getting issue relation issues + async def intake_search( + self, + info: Info, + slug: str, + project: str, + search: Optional[str] = None, + cursor: Optional[str] = None, + ) -> PaginatorResponse[IntakeWorkItemType]: + user = info.context.user + user_id = str(user.id) + + # get the workspace + workspace = await get_workspace(workspace_slug=slug) + workspace_slug = workspace.slug + + # get the project + project_details = await get_project( + workspace_slug=workspace_slug, project_id=project + ) + project_id = str(project_details.id) + + await is_project_intakes_enabled_async( + workspace_slug=workspace_slug, project_id=project_id + ) + + current_user_role = None + project_member = await get_project_member( + workspace_slug=workspace_slug, + project_id=project_id, + user_id=user_id, + raise_exception=False, + ) + if not project_member: + project_teamspace_filter = await project_member_filter_via_teamspaces_async( + user_id=user_id, + workspace_slug=workspace_slug, + ) + teamspace_project_ids = project_teamspace_filter.teamspace_project_ids + if project_id not in teamspace_project_ids: + message = "You are not allowed to access this project" + error_extensions = {"code": "FORBIDDEN", "statusCode": 403} + raise GraphQLError(message, extensions=error_extensions) + current_user_role = Roles.MEMBER.value + else: + current_user_role = project_member.role + + # get the intake work items + intake_work_items = await get_intake_work_items_async( + user_id=user_id if current_user_role == Roles.GUEST.value else None, + workspace_slug=workspace_slug, + project_id=project_id, + is_snoozed_work_items_required=False, + search=search, + ) + + return paginate(results_object=intake_work_items, cursor=cursor) diff --git a/apps/api/plane/graphql/schema.py b/apps/api/plane/graphql/schema.py index f4501406c3..0e9a03ef59 100644 --- a/apps/api/plane/graphql/schema.py +++ b/apps/api/plane/graphql/schema.py @@ -111,6 +111,7 @@ from .queries.intake import ( IntakeWorkItemCommentQuery, IntakeWorkItemCommentReactionQuery, IntakeWorkItemQuery, + IntakeSearchQuery, ) from .queries.issue import ( IssueCommentActivityQuery, @@ -283,6 +284,7 @@ class Query( IntakeWorkItemCommentQuery, IntakeWorkItemCommentReactionQuery, IntakeWorkItemAttachmentQuery, + IntakeSearchQuery, ): pass