mirror of
https://github.com/makeplane/plane.git
synced 2026-02-25 04:35:21 +01:00
dev: handled the project details and workitem identifier in user favorites and recents (#4097)
This commit is contained in:
@@ -12,7 +12,7 @@ from asgiref.sync import sync_to_async
|
||||
# Module imports
|
||||
from plane.db.models import User
|
||||
from plane.graphql.permissions.workspace import IsAuthenticated
|
||||
from plane.graphql.types.users import UserType
|
||||
from plane.graphql.types.user import UserType
|
||||
|
||||
|
||||
@strawberry.type
|
||||
|
||||
@@ -12,7 +12,7 @@ from strawberry.types import Info
|
||||
# Module imports
|
||||
from plane.db.models import Profile, WorkspaceMember
|
||||
from plane.graphql.permissions.workspace import IsAuthenticated
|
||||
from plane.graphql.types.users import ProfileType, ProfileUpdateInputType
|
||||
from plane.graphql.types.user import ProfileType, ProfileUpdateInputType
|
||||
|
||||
|
||||
@strawberry.type
|
||||
|
||||
@@ -16,7 +16,7 @@ from strawberry.types import Info
|
||||
from plane.db.models import UserFavorite, UserRecentVisit
|
||||
from plane.graphql.helpers.teamspace import project_member_filter_via_teamspaces_async
|
||||
from plane.graphql.permissions.workspace import IsAuthenticated, WorkspaceBasePermission
|
||||
from plane.graphql.types.users import UserFavoriteType, UserRecentVisitType, UserType
|
||||
from plane.graphql.types.user import UserFavoriteType, UserRecentVisitType, UserType
|
||||
from plane.graphql.utils.timezone.user import user_timezone_converter
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ class UserFavoritesQuery:
|
||||
| (Q(project__isnull=False) & Q(project_teamspace_filter.query)),
|
||||
~Q(entity_type="view"),
|
||||
)
|
||||
.prefetch_related("project")
|
||||
.order_by("-created_at")
|
||||
.distinct()
|
||||
)
|
||||
@@ -76,6 +77,7 @@ class UserRecentVisitQuery:
|
||||
recent_visits = await sync_to_async(
|
||||
lambda: list(
|
||||
UserRecentVisit.objects.filter(workspace__slug=slug, user_id=user_id)
|
||||
.prefetch_related("project")
|
||||
.exclude(entity_name__in=["view", "workspace_page"])
|
||||
.order_by("-visited_at")
|
||||
)
|
||||
@@ -105,6 +107,7 @@ class UserRecentVisitQuery:
|
||||
deleted_at=await user_timezone_converter(
|
||||
user, visit.deleted_at if visit.deleted_at else None
|
||||
),
|
||||
project_details=visit.project,
|
||||
)
|
||||
for visit in recent_visits
|
||||
]
|
||||
|
||||
@@ -10,7 +10,7 @@ from strawberry.permission import PermissionExtension
|
||||
|
||||
# Module Imports
|
||||
from plane.db.models import Profile
|
||||
from plane.graphql.types.users import ProfileType
|
||||
from plane.graphql.types.user import ProfileType
|
||||
from plane.graphql.permissions.workspace import IsAuthenticated
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ from asgiref.sync import sync_to_async
|
||||
|
||||
# Module Imports
|
||||
from plane.db.models import Cycle, Issue, CycleUserProperties
|
||||
from plane.graphql.types.users import UserType
|
||||
from plane.graphql.types.user import UserType
|
||||
|
||||
|
||||
@strawberry_django.type(Cycle)
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Optional
|
||||
import strawberry
|
||||
|
||||
# module imports
|
||||
from plane.graphql.types.users import UserType, ProfileType
|
||||
from plane.graphql.types.user import UserType, ProfileType
|
||||
from plane.graphql.types.workspace import WorkspaceType
|
||||
from plane.graphql.types.device import DeviceInformationType
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from asgiref.sync import sync_to_async
|
||||
|
||||
# Module Imports
|
||||
from plane.db.models import IssueActivity
|
||||
from plane.graphql.types.users import UserLiteType
|
||||
from plane.graphql.types.user import UserLiteType
|
||||
from plane.graphql.utils.timezone import user_timezone_converter
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ from strawberry.scalars import JSON
|
||||
|
||||
# Module Imports
|
||||
from plane.db.models import IssueComment
|
||||
from plane.graphql.types.users import UserLiteType
|
||||
from plane.graphql.types.user import UserLiteType
|
||||
from plane.graphql.utils.timezone import user_timezone_converter
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ from asgiref.sync import sync_to_async
|
||||
|
||||
# Module Imports
|
||||
from plane.db.models import IntakeIssue, IssueActivity
|
||||
from plane.graphql.types.users import UserLiteType
|
||||
from plane.graphql.types.user import UserLiteType
|
||||
from plane.graphql.utils.timezone import user_timezone_converter
|
||||
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ from strawberry.scalars import JSON
|
||||
|
||||
# Module Imports
|
||||
from plane.db.models import IssueComment
|
||||
from plane.graphql.types.users import UserLiteType
|
||||
from plane.graphql.types.user import UserLiteType
|
||||
from plane.graphql.utils.timezone import user_timezone_converter
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from asgiref.sync import sync_to_async
|
||||
|
||||
# Module Imports
|
||||
from plane.db.models import IssueActivity
|
||||
from plane.graphql.types.users import UserLiteType
|
||||
from plane.graphql.types.user import UserLiteType
|
||||
from plane.graphql.utils.timezone import user_timezone_converter
|
||||
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ from strawberry.scalars import JSON
|
||||
|
||||
# Module Imports
|
||||
from plane.db.models import IssueComment
|
||||
from plane.graphql.types.users import UserLiteType
|
||||
from plane.graphql.types.user import UserLiteType
|
||||
from plane.graphql.utils.timezone import user_timezone_converter
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from strawberry.scalars import JSON
|
||||
# Module Imports
|
||||
from plane.graphql.utils.timezone import user_timezone_converter
|
||||
from plane.db.models import Module, Issue, ModuleUserProperties
|
||||
from plane.graphql.types.users import UserType
|
||||
from plane.graphql.types.user import UserType
|
||||
|
||||
# Third-party library imports
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
@@ -13,7 +13,7 @@ from strawberry.scalars import JSON
|
||||
|
||||
from plane.db.models import Issue, Notification
|
||||
from plane.graphql.types.project import ProjectType
|
||||
from plane.graphql.types.users import UserType
|
||||
from plane.graphql.types.user import UserType
|
||||
from plane.graphql.utils.issue_activity import issue_activity_comment_string
|
||||
|
||||
# Module imports
|
||||
|
||||
@@ -172,7 +172,7 @@ class NestedParentPageLiteType:
|
||||
is_description_empty: bool
|
||||
|
||||
@strawberry.field
|
||||
def owned_by(self) -> list[ProjectLiteType]:
|
||||
def owned_by(self) -> list[strawberry.ID]:
|
||||
return self.owned_by_id
|
||||
|
||||
@strawberry.field
|
||||
|
||||
@@ -126,8 +126,8 @@ class ProjectMemberType:
|
||||
|
||||
@strawberry_django.type(Project)
|
||||
class ProjectLiteType:
|
||||
id: strawberry.ID
|
||||
name: str
|
||||
identifier: str
|
||||
is_member: Optional[bool]
|
||||
logo_props: JSON
|
||||
id: Optional[strawberry.ID] = None
|
||||
name: Optional[str] = None
|
||||
identifier: Optional[str] = None
|
||||
is_member: Optional[bool] = False
|
||||
logo_props: Optional[JSON] = None
|
||||
|
||||
@@ -1 +1,4 @@
|
||||
from .base import UserType, UserLiteType, ProfileType, ProfileUpdateInputType
|
||||
from .delete import UserDeleteType, UserDeleteInputType
|
||||
from .favorite import UserFavoriteType, UserFavoriteEntityData
|
||||
from .recent import UserRecentVisitType, UserRecentVisitEntityData
|
||||
|
||||
116
apps/api/plane/graphql/types/user/base.py
Normal file
116
apps/api/plane/graphql/types/user/base.py
Normal file
@@ -0,0 +1,116 @@
|
||||
# Python imports
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
# Strawberry imports
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
# Django Imports
|
||||
from asgiref.sync import sync_to_async
|
||||
from strawberry.scalars import JSON
|
||||
from strawberry.types import Info
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import (
|
||||
Profile,
|
||||
User,
|
||||
Workspace,
|
||||
)
|
||||
|
||||
|
||||
@strawberry_django.type(User)
|
||||
class UserType:
|
||||
id: strawberry.ID
|
||||
avatar: Optional[str]
|
||||
cover_image: Optional[str]
|
||||
date_joined: datetime
|
||||
display_name: str
|
||||
email: str
|
||||
first_name: str
|
||||
last_name: str
|
||||
is_active: bool
|
||||
is_bot: bool
|
||||
is_email_verified: bool
|
||||
user_timezone: str
|
||||
username: str
|
||||
is_password_autoset: bool
|
||||
last_login_medium: str
|
||||
avatar_url: Optional[str]
|
||||
cover_image_url: Optional[str]
|
||||
|
||||
|
||||
@strawberry_django.type(User)
|
||||
class UserLiteType:
|
||||
id: Optional[strawberry.ID] = None
|
||||
first_name: Optional[str] = None
|
||||
last_name: Optional[str] = None
|
||||
avatar: Optional[str] = None
|
||||
avatar_url: Optional[str] = None
|
||||
is_bot: bool = False
|
||||
display_name: Optional[str] = None
|
||||
|
||||
|
||||
@strawberry.input
|
||||
@dataclass
|
||||
class ProfileUpdateInputType:
|
||||
mobile_timezone_auto_set: Optional[bool] = field(default_factory=lambda: None)
|
||||
is_mobile_onboarded: Optional[bool] = field(default_factory=lambda: None)
|
||||
mobile_onboarding_step: Optional[JSON] = field(default_factory=lambda: None)
|
||||
|
||||
|
||||
@strawberry_django.type(Profile)
|
||||
class ProfileType:
|
||||
id: strawberry.ID
|
||||
user: strawberry.ID
|
||||
theme: JSON
|
||||
is_tour_completed: bool
|
||||
onboarding_step: JSON
|
||||
use_case: Optional[str]
|
||||
role: Optional[str]
|
||||
is_onboarded: bool
|
||||
last_workspace_id: Optional[strawberry.ID]
|
||||
billing_address_country: JSON
|
||||
billing_address: Optional[str]
|
||||
has_billing_address: bool
|
||||
company_name: str
|
||||
mobile_timezone_auto_set: bool
|
||||
is_mobile_onboarded: bool
|
||||
mobile_onboarding_step = Optional[JSON]
|
||||
|
||||
@strawberry.field
|
||||
def user(self) -> int:
|
||||
return self.user_id
|
||||
|
||||
@strawberry.field
|
||||
async def last_workspace_id(self, info: Info) -> Optional[strawberry.ID]:
|
||||
if (
|
||||
self.last_workspace_id is not None
|
||||
and await sync_to_async(
|
||||
Workspace.objects.filter(
|
||||
pk=self.last_workspace_id,
|
||||
workspace_member__member_id=info.context.user.id,
|
||||
workspace_member__is_active=True,
|
||||
).exists
|
||||
)()
|
||||
):
|
||||
return self.last_workspace_id
|
||||
|
||||
# Query the fallback workspace for the current user if last_workspace_id is null
|
||||
fallback_workspace = await sync_to_async(
|
||||
Workspace.objects.filter(
|
||||
workspace_member__member_id=info.context.user.id,
|
||||
workspace_member__is_active=True,
|
||||
)
|
||||
.order_by("created_at")
|
||||
.first
|
||||
)()
|
||||
|
||||
if fallback_workspace:
|
||||
profile = await sync_to_async(Profile.objects.get)(id=self.id)
|
||||
profile.last_workspace_id = fallback_workspace.id
|
||||
await sync_to_async(profile.save)()
|
||||
return fallback_workspace.id
|
||||
|
||||
return None
|
||||
143
apps/api/plane/graphql/types/user/favorite.py
Normal file
143
apps/api/plane/graphql/types/user/favorite.py
Normal file
@@ -0,0 +1,143 @@
|
||||
# Python imports
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
# Strawberry imports
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
# Django Imports
|
||||
from asgiref.sync import sync_to_async
|
||||
from strawberry.scalars import JSON
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import (
|
||||
Cycle,
|
||||
Issue,
|
||||
IssueView,
|
||||
Module,
|
||||
Page,
|
||||
Project,
|
||||
UserFavorite,
|
||||
)
|
||||
from plane.graphql.utils.timezone import user_timezone_converter
|
||||
from plane.graphql.types.project import ProjectLiteType
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class UserFavoriteEntityData:
|
||||
id: Optional[strawberry.ID] = None
|
||||
name: Optional[str] = None
|
||||
logo_props: Optional[JSON] = None
|
||||
is_epic: Optional[bool] = False
|
||||
workitem_identifier: Optional[str] = None
|
||||
|
||||
|
||||
@strawberry_django.type(UserFavorite)
|
||||
class UserFavoriteType:
|
||||
id: strawberry.ID
|
||||
entity_type: str
|
||||
entity_identifier: Optional[str]
|
||||
name: Optional[str]
|
||||
is_folder: bool
|
||||
sequence: float
|
||||
parent: Optional[strawberry.ID]
|
||||
created_at: Optional[datetime]
|
||||
updated_at: Optional[datetime]
|
||||
deleted_at: Optional[datetime]
|
||||
project: Optional[strawberry.ID]
|
||||
|
||||
@strawberry.field
|
||||
def workspace(self) -> int:
|
||||
return self.workspace_id
|
||||
|
||||
@strawberry.field
|
||||
def project(self) -> int:
|
||||
return self.project_id
|
||||
|
||||
@strawberry.field
|
||||
def project_details(self) -> ProjectLiteType:
|
||||
if self.project:
|
||||
return self.project
|
||||
return ProjectLiteType()
|
||||
|
||||
@strawberry.field
|
||||
def created_at(self, info) -> Optional[datetime]:
|
||||
converted_date = user_timezone_converter(info.context.user, self.created_at)
|
||||
return converted_date
|
||||
|
||||
@strawberry.field
|
||||
def updated_at(self, info) -> Optional[datetime]:
|
||||
converted_date = user_timezone_converter(info.context.user, self.updated_at)
|
||||
return converted_date
|
||||
|
||||
@strawberry.field
|
||||
async def entity_data(self) -> Optional[UserFavoriteEntityData]:
|
||||
# where entity_identifier is project_id and entity_type is project
|
||||
if self.entity_identifier and self.entity_type == "project":
|
||||
project = await sync_to_async(
|
||||
Project.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if project:
|
||||
return UserFavoriteEntityData(
|
||||
id=project.id, name=project.name, logo_props=project.logo_props
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is cycle_id and entity_type is cycle
|
||||
elif self.entity_identifier and self.entity_type == "cycle":
|
||||
cycle = await sync_to_async(
|
||||
Cycle.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if cycle:
|
||||
return UserFavoriteEntityData(
|
||||
id=cycle.id, name=cycle.name, logo_props=cycle.logo_props
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is module id and entity_type is module
|
||||
elif self.entity_identifier and self.entity_type == "module":
|
||||
module = await sync_to_async(
|
||||
Module.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if module:
|
||||
return UserFavoriteEntityData(
|
||||
id=module.id, name=module.name, logo_props=module.logo_props
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is issue id and entity_type is issue
|
||||
elif self.entity_identifier and self.entity_type == "issue":
|
||||
issue = await sync_to_async(
|
||||
Issue.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if issue:
|
||||
workitem_identifier = f"{self.project.identifier}-{issue.sequence_id}"
|
||||
return UserFavoriteEntityData(
|
||||
id=issue.id,
|
||||
name=issue.name,
|
||||
logo_props=None,
|
||||
workitem_identifier=workitem_identifier,
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is issue_view id and entity_type is issue_view
|
||||
elif self.entity_identifier and self.entity_type == "view":
|
||||
issue_view = await sync_to_async(
|
||||
IssueView.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if issue_view:
|
||||
return UserFavoriteEntityData(
|
||||
id=issue_view.id,
|
||||
name=issue_view.name,
|
||||
logo_props=issue_view.logo_props,
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is page id and entity_type is page
|
||||
elif self.entity_identifier and self.entity_type == "page":
|
||||
page = await sync_to_async(
|
||||
Page.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if page:
|
||||
return UserFavoriteEntityData(
|
||||
id=page.id, name=page.name, logo_props=page.logo_props
|
||||
)
|
||||
return None
|
||||
# where entity_identifier and entity_type is None
|
||||
return None
|
||||
130
apps/api/plane/graphql/types/user/recent.py
Normal file
130
apps/api/plane/graphql/types/user/recent.py
Normal file
@@ -0,0 +1,130 @@
|
||||
# Python imports
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
# Strawberry imports
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
# Django Imports
|
||||
from asgiref.sync import sync_to_async
|
||||
from django.db.models import Q
|
||||
from strawberry.scalars import JSON
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import (
|
||||
Cycle,
|
||||
Issue,
|
||||
IssueView,
|
||||
Module,
|
||||
Page,
|
||||
Project,
|
||||
UserRecentVisit,
|
||||
)
|
||||
from plane.graphql.types.project import ProjectLiteType
|
||||
|
||||
|
||||
@strawberry.type
|
||||
class UserRecentVisitEntityData:
|
||||
id: Optional[strawberry.ID] = None
|
||||
name: Optional[str] = None
|
||||
logo_props: Optional[JSON] = None
|
||||
is_epic: Optional[bool] = False
|
||||
workitem_identifier: Optional[str] = None
|
||||
|
||||
|
||||
@strawberry_django.type(UserRecentVisit)
|
||||
class UserRecentVisitType:
|
||||
id: strawberry.ID
|
||||
entity_identifier: str
|
||||
entity_name: str
|
||||
user: strawberry.ID
|
||||
visited_at: Optional[datetime]
|
||||
workspace: Optional[strawberry.ID]
|
||||
project: Optional[strawberry.ID]
|
||||
project_details: Optional[ProjectLiteType]
|
||||
created_by: Optional[strawberry.ID]
|
||||
updated_by: Optional[strawberry.ID]
|
||||
created_at: Optional[datetime]
|
||||
updated_at: Optional[datetime]
|
||||
deleted_at: Optional[datetime]
|
||||
|
||||
@strawberry.field
|
||||
async def entity_data(self) -> Optional[UserRecentVisitEntityData]:
|
||||
# where entity_identifier is project_id and entity_name is project
|
||||
if self.entity_identifier and self.entity_name == "project":
|
||||
project = await sync_to_async(
|
||||
Project.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if project:
|
||||
return UserRecentVisitEntityData(
|
||||
id=project.id, name=project.name, logo_props=project.logo_props
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is cycle_id and entity_name is cycle
|
||||
elif self.entity_identifier and self.entity_name == "cycle":
|
||||
cycle = await sync_to_async(
|
||||
Cycle.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if cycle:
|
||||
return UserRecentVisitEntityData(
|
||||
id=cycle.id, name=cycle.name, logo_props=cycle.logo_props
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is module id and entity_name is module
|
||||
elif self.entity_identifier and self.entity_name == "module":
|
||||
module = await sync_to_async(
|
||||
Module.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if module:
|
||||
return UserRecentVisitEntityData(
|
||||
id=module.id, name=module.name, logo_props=module.logo_props
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is issue id and entity_name is issue
|
||||
elif self.entity_identifier and self.entity_name == "issue":
|
||||
issue_base_query = Issue.objects.filter(id=self.entity_identifier)
|
||||
issue = await sync_to_async(issue_base_query.first)()
|
||||
if issue:
|
||||
epic_issue = await sync_to_async(
|
||||
issue_base_query.filter(
|
||||
project__project_projectfeature__is_epic_enabled=True
|
||||
)
|
||||
.filter(Q(type__isnull=False) & Q(type__is_epic=True))
|
||||
.first
|
||||
)()
|
||||
workitem_identifier = (
|
||||
f"{self.project_details.identifier}-{issue.sequence_id}"
|
||||
)
|
||||
return UserRecentVisitEntityData(
|
||||
id=issue.id,
|
||||
name=issue.name,
|
||||
logo_props=None,
|
||||
is_epic=epic_issue is not None,
|
||||
workitem_identifier=workitem_identifier,
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is issue_view id and entity_name is issue_view
|
||||
elif self.entity_identifier and self.entity_name == "view":
|
||||
issue_view = await sync_to_async(
|
||||
IssueView.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if issue_view:
|
||||
return UserRecentVisitEntityData(
|
||||
id=issue_view.id,
|
||||
name=issue_view.name,
|
||||
logo_props=issue_view.logo_props,
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is page id and entity_name is page
|
||||
elif self.entity_identifier and self.entity_name == "page":
|
||||
page = await sync_to_async(
|
||||
Page.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if page:
|
||||
return UserRecentVisitEntityData(
|
||||
id=page.id, name=page.name, logo_props=page.logo_props
|
||||
)
|
||||
return None
|
||||
# where entity_identifier and entity_name is None
|
||||
return None
|
||||
@@ -1,324 +0,0 @@
|
||||
# Python imports
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
# Strawberry imports
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
|
||||
# Django Imports
|
||||
from asgiref.sync import sync_to_async
|
||||
from django.db.models import Q
|
||||
from strawberry.scalars import JSON
|
||||
from strawberry.types import Info
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import (
|
||||
Cycle,
|
||||
Issue,
|
||||
IssueView,
|
||||
Module,
|
||||
Page,
|
||||
Profile,
|
||||
Project,
|
||||
User,
|
||||
UserFavorite,
|
||||
UserRecentVisit,
|
||||
Workspace,
|
||||
)
|
||||
from plane.graphql.utils.timezone import user_timezone_converter
|
||||
|
||||
|
||||
@strawberry_django.type(User)
|
||||
class UserType:
|
||||
id: strawberry.ID
|
||||
avatar: Optional[str]
|
||||
cover_image: Optional[str]
|
||||
date_joined: datetime
|
||||
display_name: str
|
||||
email: str
|
||||
first_name: str
|
||||
last_name: str
|
||||
is_active: bool
|
||||
is_bot: bool
|
||||
is_email_verified: bool
|
||||
user_timezone: str
|
||||
username: str
|
||||
is_password_autoset: bool
|
||||
last_login_medium: str
|
||||
avatar_url: Optional[str]
|
||||
cover_image_url: Optional[str]
|
||||
|
||||
|
||||
@strawberry_django.type(User)
|
||||
class UserLiteType:
|
||||
id: Optional[strawberry.ID] = None
|
||||
first_name: Optional[str] = None
|
||||
last_name: Optional[str] = None
|
||||
avatar: Optional[str] = None
|
||||
avatar_url: Optional[str] = None
|
||||
is_bot: bool = False
|
||||
display_name: Optional[str] = None
|
||||
|
||||
|
||||
@strawberry.input
|
||||
@dataclass
|
||||
class ProfileUpdateInputType:
|
||||
mobile_timezone_auto_set: Optional[bool] = field(default_factory=lambda: None)
|
||||
is_mobile_onboarded: Optional[bool] = field(default_factory=lambda: None)
|
||||
mobile_onboarding_step: Optional[JSON] = field(default_factory=lambda: None)
|
||||
|
||||
|
||||
@strawberry_django.type(Profile)
|
||||
class ProfileType:
|
||||
id: strawberry.ID
|
||||
user: strawberry.ID
|
||||
theme: JSON
|
||||
is_tour_completed: bool
|
||||
onboarding_step: JSON
|
||||
use_case: Optional[str]
|
||||
role: Optional[str]
|
||||
is_onboarded: bool
|
||||
last_workspace_id: Optional[strawberry.ID]
|
||||
billing_address_country: JSON
|
||||
billing_address: Optional[str]
|
||||
has_billing_address: bool
|
||||
company_name: str
|
||||
mobile_timezone_auto_set: bool
|
||||
is_mobile_onboarded: bool
|
||||
mobile_onboarding_step = Optional[JSON]
|
||||
|
||||
@strawberry.field
|
||||
def user(self) -> int:
|
||||
return self.user_id
|
||||
|
||||
@strawberry.field
|
||||
async def last_workspace_id(self, info: Info) -> Optional[strawberry.ID]:
|
||||
if (
|
||||
self.last_workspace_id is not None
|
||||
and await sync_to_async(
|
||||
Workspace.objects.filter(
|
||||
pk=self.last_workspace_id,
|
||||
workspace_member__member_id=info.context.user.id,
|
||||
workspace_member__is_active=True,
|
||||
).exists
|
||||
)()
|
||||
):
|
||||
return self.last_workspace_id
|
||||
|
||||
# Query the fallback workspace for the current user if last_workspace_id is null
|
||||
fallback_workspace = await sync_to_async(
|
||||
Workspace.objects.filter(
|
||||
workspace_member__member_id=info.context.user.id,
|
||||
workspace_member__is_active=True,
|
||||
)
|
||||
.order_by("created_at")
|
||||
.first
|
||||
)()
|
||||
|
||||
if fallback_workspace:
|
||||
profile = await sync_to_async(Profile.objects.get)(id=self.id)
|
||||
profile.last_workspace_id = fallback_workspace.id
|
||||
await sync_to_async(profile.save)()
|
||||
return fallback_workspace.id
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# user favorite
|
||||
@strawberry.type
|
||||
class UserFavoriteEntityData:
|
||||
id: Optional[strawberry.ID]
|
||||
name: Optional[str]
|
||||
logo_props: Optional[JSON]
|
||||
is_epic: Optional[bool] = False
|
||||
|
||||
|
||||
@strawberry_django.type(UserFavorite)
|
||||
class UserFavoriteType:
|
||||
id: strawberry.ID
|
||||
entity_type: str
|
||||
entity_identifier: Optional[str]
|
||||
name: Optional[str]
|
||||
is_folder: bool
|
||||
sequence: float
|
||||
parent: Optional[strawberry.ID]
|
||||
created_at: Optional[datetime]
|
||||
updated_at: Optional[datetime]
|
||||
deleted_at: Optional[datetime]
|
||||
project: Optional[strawberry.ID]
|
||||
|
||||
@strawberry.field
|
||||
def project(self) -> int:
|
||||
return self.project_id
|
||||
|
||||
@strawberry.field
|
||||
def created_at(self, info) -> Optional[datetime]:
|
||||
converted_date = user_timezone_converter(info.context.user, self.created_at)
|
||||
return converted_date
|
||||
|
||||
@strawberry.field
|
||||
def updated_at(self, info) -> Optional[datetime]:
|
||||
converted_date = user_timezone_converter(info.context.user, self.updated_at)
|
||||
return converted_date
|
||||
|
||||
@strawberry.field
|
||||
async def entity_data(self) -> Optional[UserFavoriteEntityData]:
|
||||
# where entity_identifier is project_id and entity_type is project
|
||||
if self.entity_identifier and self.entity_type == "project":
|
||||
project = await sync_to_async(
|
||||
Project.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if project:
|
||||
return UserFavoriteEntityData(
|
||||
id=project.id, name=project.name, logo_props=project.logo_props
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is cycle_id and entity_type is cycle
|
||||
elif self.entity_identifier and self.entity_type == "cycle":
|
||||
cycle = await sync_to_async(
|
||||
Cycle.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if cycle:
|
||||
return UserFavoriteEntityData(
|
||||
id=cycle.id, name=cycle.name, logo_props=cycle.logo_props
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is module id and entity_type is module
|
||||
elif self.entity_identifier and self.entity_type == "module":
|
||||
module = await sync_to_async(
|
||||
Module.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if module:
|
||||
return UserFavoriteEntityData(
|
||||
id=module.id, name=module.name, logo_props=module.logo_props
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is issue id and entity_type is issue
|
||||
elif self.entity_identifier and self.entity_type == "issue":
|
||||
issue = await sync_to_async(
|
||||
Issue.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if issue:
|
||||
return UserFavoriteEntityData(
|
||||
id=issue.id, name=issue.name, logo_props=None
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is issue_view id and entity_type is issue_view
|
||||
elif self.entity_identifier and self.entity_type == "view":
|
||||
issue_view = await sync_to_async(
|
||||
IssueView.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if issue_view:
|
||||
return UserFavoriteEntityData(
|
||||
id=issue_view.id,
|
||||
name=issue_view.name,
|
||||
logo_props=issue_view.logo_props,
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is page id and entity_type is page
|
||||
elif self.entity_identifier and self.entity_type == "page":
|
||||
page = await sync_to_async(
|
||||
Page.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if page:
|
||||
return UserFavoriteEntityData(
|
||||
id=page.id, name=page.name, logo_props=page.logo_props
|
||||
)
|
||||
return None
|
||||
# where entity_identifier and entity_type is None
|
||||
return None
|
||||
|
||||
|
||||
# user recent visit
|
||||
@strawberry_django.type(UserRecentVisit)
|
||||
class UserRecentVisitType:
|
||||
id: strawberry.ID
|
||||
entity_identifier: str
|
||||
entity_name: str
|
||||
user: strawberry.ID
|
||||
visited_at: Optional[datetime]
|
||||
workspace: Optional[strawberry.ID]
|
||||
project: Optional[strawberry.ID]
|
||||
created_by: Optional[strawberry.ID]
|
||||
updated_by: Optional[strawberry.ID]
|
||||
created_at: Optional[datetime]
|
||||
updated_at: Optional[datetime]
|
||||
deleted_at: Optional[datetime]
|
||||
|
||||
@strawberry.field
|
||||
async def entity_data(self) -> Optional[UserFavoriteEntityData]:
|
||||
# where entity_identifier is project_id and entity_name is project
|
||||
if self.entity_identifier and self.entity_name == "project":
|
||||
project = await sync_to_async(
|
||||
Project.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if project:
|
||||
return UserFavoriteEntityData(
|
||||
id=project.id, name=project.name, logo_props=project.logo_props
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is cycle_id and entity_name is cycle
|
||||
elif self.entity_identifier and self.entity_name == "cycle":
|
||||
cycle = await sync_to_async(
|
||||
Cycle.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if cycle:
|
||||
return UserFavoriteEntityData(
|
||||
id=cycle.id, name=cycle.name, logo_props=cycle.logo_props
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is module id and entity_name is module
|
||||
elif self.entity_identifier and self.entity_name == "module":
|
||||
module = await sync_to_async(
|
||||
Module.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if module:
|
||||
return UserFavoriteEntityData(
|
||||
id=module.id, name=module.name, logo_props=module.logo_props
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is issue id and entity_name is issue
|
||||
elif self.entity_identifier and self.entity_name == "issue":
|
||||
issue_base_query = Issue.objects.filter(id=self.entity_identifier)
|
||||
issue = await sync_to_async(issue_base_query.first)()
|
||||
if issue:
|
||||
epic_issue = await sync_to_async(
|
||||
issue_base_query.filter(
|
||||
project__project_projectfeature__is_epic_enabled=True
|
||||
)
|
||||
.filter(Q(type__isnull=False) & Q(type__is_epic=True))
|
||||
.first
|
||||
)()
|
||||
return UserFavoriteEntityData(
|
||||
id=issue.id,
|
||||
name=issue.name,
|
||||
logo_props=None,
|
||||
is_epic=epic_issue is not None,
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is issue_view id and entity_name is issue_view
|
||||
elif self.entity_identifier and self.entity_name == "view":
|
||||
issue_view = await sync_to_async(
|
||||
IssueView.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if issue_view:
|
||||
return UserFavoriteEntityData(
|
||||
id=issue_view.id,
|
||||
name=issue_view.name,
|
||||
logo_props=issue_view.logo_props,
|
||||
)
|
||||
return None
|
||||
# where entity_identifier is page id and entity_name is page
|
||||
elif self.entity_identifier and self.entity_name == "page":
|
||||
page = await sync_to_async(
|
||||
Page.objects.filter(id=self.entity_identifier).first
|
||||
)()
|
||||
if page:
|
||||
return UserFavoriteEntityData(
|
||||
id=page.id, name=page.name, logo_props=page.logo_props
|
||||
)
|
||||
return None
|
||||
# where entity_identifier and entity_name is None
|
||||
return None
|
||||
@@ -14,7 +14,7 @@ from strawberry.types import Info
|
||||
|
||||
# Module Imports
|
||||
from plane.db.models import Workspace, WorkspaceMember
|
||||
from plane.graphql.types.users import UserType
|
||||
from plane.graphql.types.user import UserType
|
||||
from plane.graphql.utils.timezone import user_timezone_converter
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user