From 8c471a45b649e774d9d2a334929e3365171f982d Mon Sep 17 00:00:00 2001 From: Ramesh Kumar Chandra <31303617+rameshkumarchandra@users.noreply.github.com> Date: Tue, 24 Sep 2024 20:29:27 +0530 Subject: [PATCH 1/2] chore: added user role for projects (#1190) --- apiserver/plane/graphql/types/project.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/apiserver/plane/graphql/types/project.py b/apiserver/plane/graphql/types/project.py index 6271ab0ca4..4988342a2b 100644 --- a/apiserver/plane/graphql/types/project.py +++ b/apiserver/plane/graphql/types/project.py @@ -44,6 +44,7 @@ class ProjectType: total_members: int total_issues: int total_active_issues: int + role: int @strawberry.field def workspace(self) -> int: @@ -73,6 +74,16 @@ class ProjectType: ).count() )() return projects + + @strawberry.field + async def role(self, info: strawberry.Info) -> int: + project_member = await sync_to_async( + lambda: ProjectMember.objects.get( + project_id=self.id, is_active=True, member_id=info.context.user.id + ) + )() + return project_member.role + @strawberry.field async def total_issues(self, info: Info) -> int: From ff11233541cb13eb06745230e1adb329686ad26d Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Wed, 25 Sep 2024 18:49:13 +0530 Subject: [PATCH 2/2] [MOBIL-261] chore: implemented create and remove mutations for sub issues (#1267) * chore: handled sub issues crud * chore: updated sub issues create and remove class * chore: removed print statements --- .../graphql/mutations/issues/__init__.py | 3 +- .../graphql/mutations/issues/relation.py | 1 - .../graphql/mutations/issues/sub_issue.py | 94 +++++++++++++++++++ apiserver/plane/graphql/queries/issue.py | 30 ------ .../plane/graphql/queries/issues/__init__.py | 3 +- .../plane/graphql/queries/issues/search.py | 28 ++++-- .../plane/graphql/queries/issues/sub_issue.py | 54 +++++++++++ apiserver/plane/graphql/schema.py | 14 ++- 8 files changed, 184 insertions(+), 43 deletions(-) create mode 100644 apiserver/plane/graphql/mutations/issues/sub_issue.py create mode 100644 apiserver/plane/graphql/queries/issues/sub_issue.py diff --git a/apiserver/plane/graphql/mutations/issues/__init__.py b/apiserver/plane/graphql/mutations/issues/__init__.py index 782d6bb1ea..7f1caed7f9 100644 --- a/apiserver/plane/graphql/mutations/issues/__init__.py +++ b/apiserver/plane/graphql/mutations/issues/__init__.py @@ -1,2 +1,3 @@ from .relation import IssueRelationMutation -from .comment import IssueCommentMutation \ No newline at end of file +from .comment import IssueCommentMutation +from .sub_issue import SubIssueMutation \ No newline at end of file diff --git a/apiserver/plane/graphql/mutations/issues/relation.py b/apiserver/plane/graphql/mutations/issues/relation.py index 4d14c9ae9b..510deac865 100644 --- a/apiserver/plane/graphql/mutations/issues/relation.py +++ b/apiserver/plane/graphql/mutations/issues/relation.py @@ -6,7 +6,6 @@ from strawberry.permission import PermissionExtension # Third-party imports from asgiref.sync import sync_to_async - # Module imports from plane.graphql.permissions.project import ProjectBasePermission from plane.db.models import Workspace, IssueRelation diff --git a/apiserver/plane/graphql/mutations/issues/sub_issue.py b/apiserver/plane/graphql/mutations/issues/sub_issue.py new file mode 100644 index 0000000000..9c6df50030 --- /dev/null +++ b/apiserver/plane/graphql/mutations/issues/sub_issue.py @@ -0,0 +1,94 @@ +from typing import Optional + +# Strawberry imports +import strawberry +from strawberry.types import Info +from strawberry.permission import PermissionExtension + +# Third-party imports +from asgiref.sync import sync_to_async + +# Module imports +from plane.graphql.permissions.project import ProjectMemberPermission +from plane.db.models import Issue +from plane.graphql.types.issue import IssuesType + + +# create methods +@sync_to_async +def get_sub_issues(sub_issue_ids): + return list(Issue.issue_objects.filter(id__in=sub_issue_ids)) + + +@sync_to_async +def bulk_update_issues(issues, fields): + Issue.objects.bulk_update(issues, fields, batch_size=10) + + +# remove methods +@sync_to_async +def get_sub_issue_details( + issueId: strawberry.ID, parentIssueId: strawberry.ID +): + return Issue.issue_objects.get(id=issueId, parent=parentIssueId) + + +@sync_to_async +def update_issue_parent( + issue: IssuesType, parentIssueId: Optional[strawberry.ID] = None +): + issue.parent = parentIssueId + issue.save() + return True + + +@strawberry.type +class SubIssueMutation: + # adding issue relation + @strawberry.mutation( + extensions=[ + PermissionExtension(permissions=[ProjectMemberPermission()]) + ] + ) + async def createSubIssue( + self, + info: Info, + slug: str, + project: strawberry.ID, + parentIssueId: strawberry.ID, + subIssueIds: list[strawberry.ID], + ) -> bool: + try: + sub_issues = await get_sub_issues(subIssueIds) + + for sub_issue in sub_issues: + sub_issue.parent_id = parentIssueId + + await bulk_update_issues(sub_issues, ["parent"]) + return True + except Exception: + return False + + # removing issue relation + @strawberry.mutation( + extensions=[ + PermissionExtension(permissions=[ProjectMemberPermission()]) + ] + ) + async def removeSubIssue( + self, + info: Info, + slug: str, + project: strawberry.ID, + parentIssueId: strawberry.ID, + subIssueId: strawberry.ID, + ) -> bool: + try: + sub_issue = await get_sub_issue_details(subIssueId, parentIssueId) + if not sub_issue: + return False + + await update_issue_parent(sub_issue, None) + return True + except Exception: + return False diff --git a/apiserver/plane/graphql/queries/issue.py b/apiserver/plane/graphql/queries/issue.py index 57f33ef87c..4f8803fe85 100644 --- a/apiserver/plane/graphql/queries/issue.py +++ b/apiserver/plane/graphql/queries/issue.py @@ -352,36 +352,6 @@ class WorkspaceIssuesQuery: return paginate(results_object=workspace_issues, cursor=cursor) -@strawberry.type -class SubIssuesQuery: - @strawberry.field( - extensions=[PermissionExtension(permissions=[ProjectBasePermission()])] - ) - async def sub_issues( - self, - info: Info, - slug: str, - project: strawberry.ID, - issue: strawberry.ID, - cursor: Optional[str] = None, - ) -> PaginatorResponse[IssuesType]: - sub_issues = await sync_to_async(list)( - Issue.issue_objects.filter( - workspace__slug=slug, - parent_id=issue, - ) - .filter( - project__project_projectmember__member=info.context.user, - project__project_projectmember__is_active=True, - ) - .select_related("workspace", "project", "state", "parent") - .prefetch_related("assignees", "labels") - .order_by("-created_at") - ) - - return paginate(results_object=sub_issues, cursor=cursor) - - @strawberry.type class IssueTypesTypeQuery: @strawberry.field( diff --git a/apiserver/plane/graphql/queries/issues/__init__.py b/apiserver/plane/graphql/queries/issues/__init__.py index 10a753afc8..6bb09c8637 100644 --- a/apiserver/plane/graphql/queries/issues/__init__.py +++ b/apiserver/plane/graphql/queries/issues/__init__.py @@ -1,2 +1,3 @@ from .relation import IssueRelationQuery -from .search import IssuesSearchQuery \ No newline at end of file +from .search import IssuesSearchQuery +from .sub_issue import SubIssuesQuery \ No newline at end of file diff --git a/apiserver/plane/graphql/queries/issues/search.py b/apiserver/plane/graphql/queries/issues/search.py index fd98050314..244c868185 100644 --- a/apiserver/plane/graphql/queries/issues/search.py +++ b/apiserver/plane/graphql/queries/issues/search.py @@ -37,10 +37,11 @@ class IssuesSearchQuery: info: Info, slug: str, project: Optional[strawberry.ID] = None, - module: Optional[strawberry.ID] = None, - cycle: Optional[strawberry.ID] = None, issue: Optional[strawberry.ID] = None, - relation_type: Optional[bool] = False, + module: Optional[strawberry.ID] = False, + cycle: Optional[bool] = False, + relationType: Optional[bool] = False, + subIssues: Optional[bool] = False, search: Optional[str] = None, ) -> list[IssueLiteType]: issue_queryset = Issue.issue_objects.filter( @@ -56,16 +57,16 @@ class IssuesSearchQuery: # module issues if module: - issue_queryset = issue_queryset.filter( - issue_module__module_id=module + issue_queryset = issue_queryset.exclude( + issue_module__module=module ) # cycle issues if cycle: - issue_queryset = issue_queryset.filter(issue_cycle__cycle_id=cycle) + issue_queryset = issue_queryset.exclude(issue_cycle__isnull=False) # issue relation issues - if relation_type and issue: + if relationType and issue: issue_queryset = issue_queryset.filter( ~Q(pk=issue), ~Q( @@ -78,6 +79,19 @@ class IssuesSearchQuery: ), ) + # sub issues + if subIssues and issue: + current_issue = await sync_to_async(Issue.issue_objects.get)( + pk=issue + ) + issue_queryset = issue_queryset.filter( + Q(parent__isNull=True), ~Q(parent=issue) + ) + if current_issue.parent: + issue_queryset = issue_queryset.filter( + ~Q(parent=current_issue.parent) + ) + # apply search filter q = Q() if search: diff --git a/apiserver/plane/graphql/queries/issues/sub_issue.py b/apiserver/plane/graphql/queries/issues/sub_issue.py new file mode 100644 index 0000000000..828243812f --- /dev/null +++ b/apiserver/plane/graphql/queries/issues/sub_issue.py @@ -0,0 +1,54 @@ +# Third-Party Imports +import strawberry + +# Python Standard Library Imports +from asgiref.sync import sync_to_async +from typing import Optional + +# Strawberry Imports +from strawberry.types import Info +from strawberry.permission import PermissionExtension + + +# Module Imports +from plane.graphql.types.issue import ( + IssuesType, +) +from plane.db.models import ( + Issue, +) + + +from plane.graphql.permissions.project import ProjectBasePermission +from plane.graphql.types.paginator import PaginatorResponse +from plane.graphql.utils.paginator import paginate + + +@strawberry.type +class SubIssuesQuery: + @strawberry.field( + extensions=[PermissionExtension(permissions=[ProjectBasePermission()])] + ) + async def sub_issues( + self, + info: Info, + slug: str, + project: strawberry.ID, + issue: strawberry.ID, + cursor: Optional[str] = None, + ) -> PaginatorResponse[IssuesType]: + sub_issues = await sync_to_async(list)( + Issue.issue_objects.filter( + workspace__slug=slug, + parent_id=issue, + ) + .filter( + project__project_projectmember__member=info.context.user, + project__project_projectmember__is_active=True, + ) + .select_related("workspace", "project", "state", "parent") + .prefetch_related("assignees", "labels") + .order_by("-created_at") + ) + + return paginate(results_object=sub_issues, cursor=cursor) diff --git a/apiserver/plane/graphql/schema.py b/apiserver/plane/graphql/schema.py index 8e7d66060a..7085a9eccb 100644 --- a/apiserver/plane/graphql/schema.py +++ b/apiserver/plane/graphql/schema.py @@ -22,7 +22,6 @@ from .queries.issue import ( IssueUserPropertyQuery, IssuePropertiesActivityQuery, IssueCommentActivityQuery, - SubIssuesQuery, IssueTypesTypeQuery, ) from .queries.page import PageQuery, UserPageQuery @@ -42,7 +41,11 @@ from .queries.search import GlobalSearchQuery from .queries.attachment import IssueAttachmentQuery from .queries.link import IssueLinkQuery from .queries.estimate import EstimatePointQuery -from .queries.issues import IssueRelationQuery, IssuesSearchQuery +from .queries.issues import ( + IssueRelationQuery, + IssuesSearchQuery, + SubIssuesQuery, +) # mutations from .mutations.workspace import WorkspaceMutation, WorkspaceInviteMutation @@ -72,7 +75,11 @@ from .mutations.module import ( ) from .mutations.link import IssueLinkMutation from .mutations.favorite import UserFavoriteMutation -from .mutations.issues import IssueRelationMutation, IssueCommentMutation +from .mutations.issues import ( + IssueRelationMutation, + IssueCommentMutation, + SubIssueMutation, +) # combined query class for all @@ -147,6 +154,7 @@ class Mutation( ModuleIssueUserPropertyMutation, IssueRelationMutation, IssueCommentMutation, + SubIssueMutation, ): pass