mirror of
https://github.com/makeplane/plane.git
synced 2026-07-12 05:21:14 +02:00
chore: cycle and module endpoints (#790)
* chore: module and cycles endpoint * chore: CRUD operation for cycle and modules * chore: resolvers in projects cycles
This commit is contained in:
committed by
GitHub
parent
a1795bdc64
commit
de4d95d90c
157
apiserver/plane/graphql/mutations/cycle.py
Normal file
157
apiserver/plane/graphql/mutations/cycle.py
Normal file
@@ -0,0 +1,157 @@
|
||||
# Python imports
|
||||
import json
|
||||
|
||||
# Strawberry imports
|
||||
import strawberry
|
||||
from strawberry.types import Info
|
||||
from strawberry.scalars import JSON
|
||||
from strawberry.permission import PermissionExtension
|
||||
|
||||
# Third-party imports
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
# Django imports
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
from django.core import serializers
|
||||
|
||||
# Module imports
|
||||
from plane.graphql.permissions.project import (
|
||||
ProjectMemberPermission,
|
||||
)
|
||||
from plane.db.models import (
|
||||
CycleIssue,
|
||||
Cycle,
|
||||
)
|
||||
from plane.graphql.bgtasks.issue_activity_task import issue_activity
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class CycleIssueMutation:
|
||||
@strawberry.mutation(
|
||||
extensions=[
|
||||
PermissionExtension(permissions=[ProjectMemberPermission()])
|
||||
]
|
||||
)
|
||||
async def create_cycle_issue(
|
||||
self,
|
||||
info: Info,
|
||||
slug: str,
|
||||
project: strawberry.ID,
|
||||
cycle: strawberry.ID,
|
||||
issues: JSON,
|
||||
) -> bool:
|
||||
# Fetch the cycle object asynchronously
|
||||
cycle = await sync_to_async(Cycle.objects.get)(
|
||||
workspace__slug=slug, project_id=project, pk=cycle
|
||||
)
|
||||
|
||||
# Fetch existing CycleIssues for the given issues asynchronously
|
||||
existing_cycle_issues = await sync_to_async(
|
||||
lambda: list(
|
||||
CycleIssue.objects.filter(
|
||||
~Q(cycle_id=cycle), issue_id__in=issues
|
||||
).all()
|
||||
)
|
||||
)()
|
||||
|
||||
# Extract issue_ids from the existing CycleIssue objects
|
||||
existing_issue_ids = set(
|
||||
map(lambda ci: str(ci.issue_id), existing_cycle_issues)
|
||||
)
|
||||
|
||||
# Convert issues to a set of strings
|
||||
new_issue_ids = set(map(str, issues))
|
||||
|
||||
# Determine new issues to create
|
||||
new_issue_ids -= existing_issue_ids
|
||||
|
||||
# Update existing CycleIssues to the new cycle asynchronously
|
||||
await sync_to_async(
|
||||
lambda: CycleIssue.objects.filter(
|
||||
~Q(cycle=cycle), issue_id__in=existing_issue_ids
|
||||
).update(cycle=cycle)
|
||||
)()
|
||||
|
||||
# Create new CycleIssues for new issues
|
||||
new_cycle_issues = [
|
||||
CycleIssue(
|
||||
project_id=project,
|
||||
workspace_id=cycle.workspace_id,
|
||||
created_by_id=info.context.user.id,
|
||||
updated_by_id=info.context.user.id,
|
||||
cycle=cycle,
|
||||
issue_id=issue_id,
|
||||
)
|
||||
for issue_id in new_issue_ids
|
||||
]
|
||||
created_records = await sync_to_async(CycleIssue.objects.bulk_create)(
|
||||
new_cycle_issues, batch_size=10
|
||||
)
|
||||
|
||||
update_cycle_issue_activity = [
|
||||
{
|
||||
"old_cycle_id": str(cycle_issue.cycle_id),
|
||||
"new_cycle_id": str(cycle.id),
|
||||
"issue_id": str(cycle_issue.issue_id),
|
||||
}
|
||||
for cycle_issue in existing_cycle_issues
|
||||
]
|
||||
|
||||
# Capture Issue Activity asynchronously
|
||||
await sync_to_async(issue_activity.delay)(
|
||||
type="cycle.activity.created",
|
||||
requested_data=json.dumps({"cycles_list": list(issues)}),
|
||||
actor_id=str(info.context.user.id),
|
||||
issue_id=None,
|
||||
project_id=str(project),
|
||||
current_instance=json.dumps(
|
||||
{
|
||||
"updated_cycle_issues": update_cycle_issue_activity,
|
||||
"created_cycle_issues": serializers.serialize(
|
||||
"json", created_records
|
||||
),
|
||||
}
|
||||
),
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
origin=info.context.request.META.get("HTTP_ORIGIN"),
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
@strawberry.mutation(
|
||||
extensions=[
|
||||
PermissionExtension(permissions=[ProjectMemberPermission()])
|
||||
]
|
||||
)
|
||||
async def delete_cycle_issue(
|
||||
self,
|
||||
info: Info,
|
||||
slug: str,
|
||||
project: strawberry.ID,
|
||||
cycle: strawberry.ID,
|
||||
issue: strawberry.ID,
|
||||
) -> bool:
|
||||
cycle_issue = await sync_to_async(CycleIssue.objects.filter)(
|
||||
cycle_id=cycle, project_id=project, workspace__slug=slug, issue_id=issue
|
||||
)
|
||||
await sync_to_async(issue_activity.delay)(
|
||||
type="cycle.activity.deleted",
|
||||
requested_data=json.dumps(
|
||||
{
|
||||
"cycle_id": str(cycle),
|
||||
"issues": [str(issue)],
|
||||
}
|
||||
),
|
||||
actor_id=str(info.context.user.id),
|
||||
issue_id=str(issue),
|
||||
project_id=str(project),
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
origin=info.context.request.META.get("HTTP_ORIGIN"),
|
||||
)
|
||||
await sync_to_async(cycle_issue.delete)()
|
||||
|
||||
return True
|
||||
@@ -6,6 +6,7 @@ import strawberry
|
||||
from strawberry.types import Info
|
||||
from strawberry.scalars import JSON
|
||||
from strawberry.permission import PermissionExtension
|
||||
from strawberry.file_uploads import Upload
|
||||
|
||||
# Third-party imports
|
||||
from typing import Optional
|
||||
@@ -262,7 +263,28 @@ class IssueUserPropertyMutation:
|
||||
|
||||
@strawberry.type
|
||||
class IssueAttachmentMutation:
|
||||
@strawberry.field(
|
||||
# @strawberry.field(
|
||||
# extensions=[PermissionExtension(permissions=[ProjectBasePermission()])]
|
||||
# )
|
||||
# async def create_issue_attachment(
|
||||
# ) -> IssueAttachment:
|
||||
# pass
|
||||
|
||||
|
||||
# @strawberry.mutation(
|
||||
# extensions=[PermissionExtension(permissions=[ProjectBasePermission()])]
|
||||
# )
|
||||
# def upload_file(self, file: Upload, info: Info) -> bool:
|
||||
# content = file.read()
|
||||
# filename = file.filename
|
||||
|
||||
# # Save the file using Django's file storage
|
||||
# # file_name = default_storage.save(filename, content)
|
||||
|
||||
# return True
|
||||
|
||||
|
||||
@strawberry.mutation(
|
||||
extensions=[PermissionExtension(permissions=[ProjectBasePermission()])]
|
||||
)
|
||||
async def deleteIssueAttachment(
|
||||
|
||||
117
apiserver/plane/graphql/mutations/module.py
Normal file
117
apiserver/plane/graphql/mutations/module.py
Normal file
@@ -0,0 +1,117 @@
|
||||
# Python imports
|
||||
import json
|
||||
|
||||
# Strawberry imports
|
||||
import strawberry
|
||||
from strawberry.scalars import JSON
|
||||
from strawberry.types import Info
|
||||
from strawberry.permission import PermissionExtension
|
||||
|
||||
# Third-party imports
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
# Django imports
|
||||
from django.utils import timezone
|
||||
|
||||
# Module imports
|
||||
from plane.graphql.permissions.project import (
|
||||
ProjectMemberPermission,
|
||||
)
|
||||
from plane.db.models import (
|
||||
Project,
|
||||
ModuleIssue,
|
||||
)
|
||||
from plane.graphql.bgtasks.issue_activity_task import issue_activity
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ModuleIssueMutation:
|
||||
@strawberry.mutation(
|
||||
extensions=[
|
||||
PermissionExtension(permissions=[ProjectMemberPermission()])
|
||||
]
|
||||
)
|
||||
async def create_module_issues(
|
||||
self,
|
||||
info: Info,
|
||||
slug: str,
|
||||
project: strawberry.ID,
|
||||
module: strawberry.ID,
|
||||
issues: JSON,
|
||||
) -> bool:
|
||||
|
||||
project = await sync_to_async(Project.objects.get)(pk=project)
|
||||
print("issue", issues)
|
||||
# Create ModuleIssues asynchronously
|
||||
await sync_to_async(
|
||||
lambda: ModuleIssue.objects.bulk_create(
|
||||
[
|
||||
ModuleIssue(
|
||||
issue_id=issue_id,
|
||||
module_id=module,
|
||||
project_id=project.id,
|
||||
workspace_id=project.workspace_id,
|
||||
created_by_id=info.context.user.id,
|
||||
updated_by_id=info.context.user.id,
|
||||
)
|
||||
for issue_id in issues
|
||||
],
|
||||
batch_size=10,
|
||||
ignore_conflicts=True,
|
||||
)
|
||||
)()
|
||||
|
||||
await sync_to_async(
|
||||
lambda: [
|
||||
issue_activity.delay(
|
||||
type="module.activity.created",
|
||||
requested_data=json.dumps({"module_id": str(module)}),
|
||||
actor_id=str(info.context.user.id),
|
||||
issue_id=str(issue),
|
||||
project_id=str(project.id),
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
origin=info.context.request.META.get("HTTP_ORIGIN"),
|
||||
)
|
||||
for issue in issues
|
||||
]
|
||||
)()
|
||||
|
||||
return True
|
||||
|
||||
@strawberry.mutation(
|
||||
extensions=[
|
||||
PermissionExtension(permissions=[ProjectMemberPermission()])
|
||||
]
|
||||
)
|
||||
async def delete_module_issue(
|
||||
self,
|
||||
info: Info,
|
||||
slug: str,
|
||||
project: strawberry.ID,
|
||||
module: strawberry.ID,
|
||||
issue: strawberry.ID,
|
||||
) -> bool:
|
||||
module_issue = await sync_to_async(ModuleIssue.objects.filter)(
|
||||
module_id=module,
|
||||
project_id=project,
|
||||
workspace__slug=slug,
|
||||
issue_id=issue,
|
||||
)
|
||||
await sync_to_async(issue_activity.delay)(
|
||||
requested_data=json.dumps({"module_id": str(module)}),
|
||||
actor_id=str(info.context.user.id),
|
||||
issue_id=str(issue),
|
||||
project_id=str(project),
|
||||
current_instance=None,
|
||||
type="module.activity.deleted",
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
notification=True,
|
||||
origin=info.context.request.META.get("HTTP_ORIGIN"),
|
||||
)
|
||||
|
||||
# Delete the module issue in a synchronous context
|
||||
await sync_to_async(module_issue.delete)()
|
||||
|
||||
return True
|
||||
82
apiserver/plane/graphql/queries/cycle.py
Normal file
82
apiserver/plane/graphql/queries/cycle.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# Third-Party Imports
|
||||
import strawberry
|
||||
|
||||
# Python Standard Library Imports
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
# Strawberry Imports
|
||||
from strawberry.types import Info
|
||||
from strawberry.permission import PermissionExtension
|
||||
|
||||
# Module Imports
|
||||
from plane.db.models import Cycle, Issue
|
||||
from plane.graphql.types.cycle import CycleType
|
||||
from plane.graphql.types.issue import IssueType
|
||||
from plane.graphql.permissions.project import ProjectBasePermission
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class CycleQuery:
|
||||
@strawberry.field(
|
||||
extensions=[PermissionExtension(permissions=[ProjectBasePermission()])]
|
||||
)
|
||||
async def cycles(
|
||||
self,
|
||||
info: Info,
|
||||
slug: str,
|
||||
project: strawberry.ID,
|
||||
) -> list[CycleType]:
|
||||
|
||||
cycles = await sync_to_async(list)(
|
||||
Cycle.objects.filter(workspace__slug=slug)
|
||||
.filter(project_id=project)
|
||||
.filter(
|
||||
project__project_projectmember__member=info.context.user,
|
||||
project__project_projectmember__is_active=True,
|
||||
)
|
||||
)
|
||||
return cycles
|
||||
|
||||
@strawberry.field(
|
||||
extensions=[PermissionExtension(permissions=[ProjectBasePermission()])]
|
||||
)
|
||||
async def cycle(
|
||||
self,
|
||||
info: Info,
|
||||
slug: str,
|
||||
project: strawberry.ID,
|
||||
cycle: strawberry.ID,
|
||||
) -> CycleType:
|
||||
cycle = await sync_to_async(Cycle.objects.get)(
|
||||
workspace__slug=slug,
|
||||
project_id=project,
|
||||
id=cycle,
|
||||
project__project_projectmember__member=info.context.user,
|
||||
project__project_projectmember__is_active=True,
|
||||
)
|
||||
return cycle
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class CycleIssueQuery:
|
||||
@strawberry.field(
|
||||
extensions=[PermissionExtension(permissions=[ProjectBasePermission()])]
|
||||
)
|
||||
async def cycleIssues(
|
||||
self,
|
||||
info: Info,
|
||||
slug: str,
|
||||
project: strawberry.ID,
|
||||
cycle: strawberry.ID,
|
||||
) -> list[IssueType]:
|
||||
|
||||
cycles_issues = await sync_to_async(list)(
|
||||
Issue.issue_objects.filter(workspace__slug=slug)
|
||||
.filter(project_id=project)
|
||||
.filter(issue_cycle__cycle_id=cycle)
|
||||
.filter(
|
||||
project__project_projectmember__member=info.context.user,
|
||||
project__project_projectmember__is_active=True,
|
||||
)
|
||||
)
|
||||
return cycles_issues
|
||||
@@ -45,9 +45,16 @@ class IssueQuery:
|
||||
filters: Optional[JSON] = {},
|
||||
orderBy: Optional[str] = "-created_at",
|
||||
groupBy: Optional[str] = None,
|
||||
type: Optional[str] = "all",
|
||||
) -> list[IssueType]:
|
||||
|
||||
filters = issue_filters(filters, "POST")
|
||||
|
||||
if type == "backlog":
|
||||
filters["state__group__in"] = ["backlog"]
|
||||
elif type == "active":
|
||||
filters["state__group__in"] = ["unstarted", "started"]
|
||||
|
||||
issues = await sync_to_async(list)(
|
||||
Issue.issue_objects.filter(
|
||||
workspace__slug=slug, project_id=project
|
||||
@@ -114,7 +121,9 @@ class RecentIssuesQuery:
|
||||
).filter(
|
||||
project__project_projectmember__member=info.context.user,
|
||||
project__project_projectmember__is_active=True,
|
||||
)[:5]
|
||||
)[
|
||||
:5
|
||||
]
|
||||
)
|
||||
|
||||
return issues
|
||||
|
||||
82
apiserver/plane/graphql/queries/module.py
Normal file
82
apiserver/plane/graphql/queries/module.py
Normal file
@@ -0,0 +1,82 @@
|
||||
# Third-Party Imports
|
||||
import strawberry
|
||||
|
||||
# Python Standard Library Imports
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
# Strawberry Imports
|
||||
from strawberry.types import Info
|
||||
from strawberry.permission import PermissionExtension
|
||||
|
||||
# Module Imports
|
||||
from plane.db.models import Module, Issue
|
||||
from plane.graphql.types.module import ModuleType
|
||||
from plane.graphql.types.issue import IssueType
|
||||
from plane.graphql.permissions.project import ProjectBasePermission
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ModuleQuery:
|
||||
@strawberry.field(
|
||||
extensions=[PermissionExtension(permissions=[ProjectBasePermission()])]
|
||||
)
|
||||
async def modules(
|
||||
self,
|
||||
info: Info,
|
||||
slug: str,
|
||||
project: strawberry.ID,
|
||||
) -> list[ModuleType]:
|
||||
|
||||
modules = await sync_to_async(list)(
|
||||
Module.objects.filter(workspace__slug=slug)
|
||||
.filter(project_id=project)
|
||||
.filter(
|
||||
project__project_projectmember__member=info.context.user,
|
||||
project__project_projectmember__is_active=True,
|
||||
)
|
||||
)
|
||||
return modules
|
||||
|
||||
@strawberry.field(
|
||||
extensions=[PermissionExtension(permissions=[ProjectBasePermission()])]
|
||||
)
|
||||
async def module(
|
||||
self,
|
||||
info: Info,
|
||||
slug: str,
|
||||
project: strawberry.ID,
|
||||
module: strawberry.ID,
|
||||
) -> ModuleType:
|
||||
module = await sync_to_async(Module.objects.get)(
|
||||
workspace__slug=slug,
|
||||
project_id=project,
|
||||
id=module,
|
||||
project__project_projectmember__member=info.context.user,
|
||||
project__project_projectmember__is_active=True,
|
||||
)
|
||||
return module
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class ModuleIssueQuery:
|
||||
@strawberry.field(
|
||||
extensions=[PermissionExtension(permissions=[ProjectBasePermission()])]
|
||||
)
|
||||
async def moduleIssues(
|
||||
self,
|
||||
info: Info,
|
||||
slug: str,
|
||||
project: strawberry.ID,
|
||||
module: strawberry.ID,
|
||||
) -> list[IssueType]:
|
||||
|
||||
module_issues = await sync_to_async(list)(
|
||||
Issue.issue_objects.filter(workspace__slug=slug)
|
||||
.filter(project_id=project)
|
||||
.filter(issue_module__module_id=module)
|
||||
.filter(
|
||||
project__project_projectmember__member=info.context.user,
|
||||
project__project_projectmember__is_active=True,
|
||||
)
|
||||
)
|
||||
return module_issues
|
||||
@@ -21,6 +21,8 @@ from .queries.issue import (
|
||||
IssueCommentActivityQuery,
|
||||
)
|
||||
from .queries.page import PageQuery
|
||||
from .queries.cycle import CycleQuery, CycleIssueQuery
|
||||
from .queries.module import ModuleQuery, ModuleIssueQuery
|
||||
from .queries.search import ProjectSearchQuery
|
||||
from .queries.attachment import IssueAttachmentQuery
|
||||
|
||||
@@ -39,7 +41,8 @@ from .mutations.issue import (
|
||||
from .mutations.notification import NotificationMutation
|
||||
from .mutations.user import ProfileMutation
|
||||
from .mutations.page import PageFavoriteMutation
|
||||
|
||||
from .mutations.cycle import CycleIssueMutation
|
||||
from .mutations.module import ModuleIssueMutation
|
||||
|
||||
# combined query class for all
|
||||
@strawberry.type
|
||||
@@ -64,6 +67,10 @@ class Query(
|
||||
WorkspaceStateQuery,
|
||||
ProjectSearchQuery,
|
||||
IssueAttachmentQuery,
|
||||
CycleQuery,
|
||||
CycleIssueQuery,
|
||||
ModuleQuery,
|
||||
ModuleIssueQuery,
|
||||
):
|
||||
pass
|
||||
|
||||
@@ -82,6 +89,8 @@ class Mutation(
|
||||
ProfileMutation,
|
||||
PageFavoriteMutation,
|
||||
IssueAttachmentMutation,
|
||||
CycleIssueMutation,
|
||||
ModuleIssueMutation,
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
76
apiserver/plane/graphql/types/cycle.py
Normal file
76
apiserver/plane/graphql/types/cycle.py
Normal file
@@ -0,0 +1,76 @@
|
||||
# python imports
|
||||
from typing import Optional
|
||||
from datetime import date, datetime
|
||||
|
||||
# Strawberry imports
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
from strawberry.types import Info
|
||||
from strawberry.scalars import JSON
|
||||
|
||||
# Third-party library imports
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
|
||||
# Module Imports
|
||||
from plane.db.models import Cycle, Issue
|
||||
|
||||
|
||||
@strawberry_django.type(Cycle)
|
||||
class CycleType:
|
||||
id: strawberry.ID
|
||||
name: str
|
||||
description: Optional[str]
|
||||
start_date: Optional[date]
|
||||
end_date: Optional[date]
|
||||
owned_by: strawberry.ID
|
||||
view_props: Optional[JSON]
|
||||
sort_order: Optional[float]
|
||||
external_source: Optional[str]
|
||||
external_id: Optional[strawberry.ID]
|
||||
progress_snapshot: Optional[JSON]
|
||||
archived_at: Optional[datetime]
|
||||
logo_props: Optional[JSON]
|
||||
project: strawberry.ID
|
||||
workspace: strawberry.ID
|
||||
created_by: strawberry.ID
|
||||
updated_by: strawberry.ID
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
total_issues: int
|
||||
completed_issues: int
|
||||
|
||||
@strawberry.field
|
||||
def owned_by(self) -> int:
|
||||
return self.owned_by_id
|
||||
|
||||
@strawberry.field
|
||||
def project(self) -> int:
|
||||
return self.project_id
|
||||
|
||||
@strawberry.field
|
||||
def workspace(self) -> int:
|
||||
return self.workspace_id
|
||||
|
||||
@strawberry.field
|
||||
def created_by(self) -> int:
|
||||
return self.created_by_id
|
||||
|
||||
@strawberry.field
|
||||
async def total_issues(self, info: Info) -> int:
|
||||
total_issues = await sync_to_async(
|
||||
lambda: Issue.issue_objects.filter(
|
||||
issue_cycle__cycle_id=self.id
|
||||
).count()
|
||||
)()
|
||||
return total_issues
|
||||
|
||||
@strawberry.field
|
||||
async def completed_issues(self, info: Info) -> int:
|
||||
total_issues = await sync_to_async(
|
||||
lambda: Issue.issue_objects.filter(
|
||||
issue_cycle__cycle_id=self.id,
|
||||
state__group="completed"
|
||||
).count()
|
||||
)()
|
||||
return total_issues
|
||||
@@ -9,6 +9,8 @@ from asgiref.sync import sync_to_async
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
from strawberry.scalars import JSON
|
||||
from strawberry.types import Info
|
||||
|
||||
|
||||
# Module Imports
|
||||
from plane.db.models import (
|
||||
@@ -16,6 +18,8 @@ from plane.db.models import (
|
||||
IssueUserProperty,
|
||||
IssueActivity,
|
||||
IssueComment,
|
||||
CycleIssue,
|
||||
ModuleIssue,
|
||||
)
|
||||
|
||||
|
||||
@@ -48,6 +52,8 @@ class IssueType:
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
sequence_id: int
|
||||
cycle: Optional[strawberry.ID]
|
||||
modules: Optional[list[strawberry.ID]]
|
||||
|
||||
@strawberry.field
|
||||
def state(self) -> int:
|
||||
@@ -83,13 +89,28 @@ class IssueType:
|
||||
labels = await sync_to_async(list)(self.labels.all())
|
||||
return [label.id for label in labels]
|
||||
|
||||
# @strawberry.field
|
||||
# async def attachments(self) -> list[JSON]:
|
||||
# return await sync_to_async(list) list(
|
||||
# IssueAttachment.objects.filter(issue_id=self.id).values(
|
||||
# "id", "attributes", "asset"
|
||||
# )
|
||||
# )
|
||||
@strawberry.field
|
||||
async def cycle(self, info: Info) -> strawberry.ID:
|
||||
cycle_issue = await sync_to_async(
|
||||
CycleIssue.objects.filter(issue_id=self.id).first
|
||||
)()
|
||||
if cycle_issue:
|
||||
return str(cycle_issue.cycle_id)
|
||||
return None
|
||||
|
||||
@strawberry.field
|
||||
async def modules(self, info: Info) -> list[strawberry.ID]:
|
||||
# Fetch related module IDs in a synchronous context
|
||||
module_issues = await sync_to_async(
|
||||
lambda: list(
|
||||
ModuleIssue.objects.filter(issue_id=self.id).values_list(
|
||||
"module_id", flat=True
|
||||
)
|
||||
)
|
||||
)()
|
||||
|
||||
# Return the module IDs as strings
|
||||
return [str(module_id) for module_id in module_issues]
|
||||
|
||||
|
||||
@strawberry_django.type(IssueUserProperty)
|
||||
|
||||
72
apiserver/plane/graphql/types/module.py
Normal file
72
apiserver/plane/graphql/types/module.py
Normal file
@@ -0,0 +1,72 @@
|
||||
# python imports
|
||||
from typing import Optional
|
||||
from datetime import date, datetime
|
||||
|
||||
# Strawberry imports
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
from strawberry.types import Info
|
||||
from strawberry.scalars import JSON
|
||||
|
||||
# Module Imports
|
||||
from plane.db.models import Module, Issue
|
||||
|
||||
# Third-party library imports
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
@strawberry_django.type(Module)
|
||||
class ModuleType:
|
||||
name: str
|
||||
status: str
|
||||
id: strawberry.ID
|
||||
project: strawberry.ID
|
||||
workspace: strawberry.ID
|
||||
created_by: strawberry.ID
|
||||
updated_by: strawberry.ID
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
description: Optional[str]
|
||||
description_text: Optional[str]
|
||||
description_html: Optional[str]
|
||||
start_date: Optional[date]
|
||||
target_date: Optional[date]
|
||||
lead: Optional[strawberry.ID]
|
||||
members: Optional[list[strawberry.ID]]
|
||||
view_props: Optional[JSON]
|
||||
sort_order: float
|
||||
external_source: Optional[str]
|
||||
external_id: Optional[strawberry.ID]
|
||||
archived_at: Optional[datetime]
|
||||
logo_props: Optional[JSON]
|
||||
total_issues: int
|
||||
completed_issues: int
|
||||
|
||||
@strawberry.field
|
||||
def project(self) -> int:
|
||||
return self.project_id
|
||||
|
||||
@strawberry.field
|
||||
def workspace(self) -> int:
|
||||
return self.workspace_id
|
||||
|
||||
@strawberry.field
|
||||
def created_by(self) -> int:
|
||||
return self.created_by_id
|
||||
|
||||
@strawberry.field
|
||||
async def total_issues(self, info: Info) -> int:
|
||||
total_issues = await sync_to_async(
|
||||
lambda: Issue.issue_objects.filter(
|
||||
issue_module__module_id=self.id
|
||||
).count()
|
||||
)()
|
||||
return total_issues
|
||||
|
||||
@strawberry.field
|
||||
async def completed_issues(self, info: Info) -> int:
|
||||
total_issues = await sync_to_async(
|
||||
lambda: Issue.issue_objects.filter(
|
||||
issue_module__module_id=self.id, state__group="completed"
|
||||
).count()
|
||||
)()
|
||||
return total_issues
|
||||
@@ -5,10 +5,14 @@ from datetime import datetime
|
||||
# Strawberry imports
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
from strawberry.types import Info
|
||||
from strawberry.scalars import JSON
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import Project, ProjectMember
|
||||
from plane.db.models import Project, ProjectMember, Issue
|
||||
|
||||
# Third-party library imports
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
|
||||
@strawberry_django.type(Project)
|
||||
@@ -37,6 +41,8 @@ class ProjectType:
|
||||
archived_at: Optional[datetime]
|
||||
is_member: bool
|
||||
is_favorite: bool
|
||||
total_members: int
|
||||
total_issues: int
|
||||
|
||||
@strawberry.field
|
||||
def workspace(self) -> int:
|
||||
@@ -58,6 +64,22 @@ class ProjectType:
|
||||
def default_state(self) -> int:
|
||||
return self.default_state_id
|
||||
|
||||
@strawberry.field
|
||||
async def total_members(self, info: strawberry.Info) -> int:
|
||||
projects = await sync_to_async(
|
||||
lambda: ProjectMember.objects.filter(
|
||||
project_id=self.id, is_active=True
|
||||
).count()
|
||||
)()
|
||||
return projects
|
||||
|
||||
@strawberry.field
|
||||
async def total_issues(self, info: Info) -> int:
|
||||
projects = await sync_to_async(
|
||||
lambda: Issue.issue_objects.filter(project_id=self.id).count()
|
||||
)()
|
||||
return projects
|
||||
|
||||
|
||||
@strawberry_django.type(ProjectMember)
|
||||
class ProjectMemberType:
|
||||
|
||||
Reference in New Issue
Block a user