mirror of
https://github.com/makeplane/plane.git
synced 2026-07-14 06:25:58 +02:00
promote: mobile-runway to preview #3603
This commit is contained in:
@@ -12,8 +12,9 @@ from strawberry.types import Info
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import Page, ProjectMember, WorkspaceMember
|
||||
from plane.graphql.permissions.workspace import WorkspaceBasePermission
|
||||
from plane.graphql.permissions.workspace import WorkspacePermission
|
||||
from plane.graphql.types.page import PageType
|
||||
from plane.graphql.utils.roles import Roles
|
||||
|
||||
|
||||
@sync_to_async
|
||||
@@ -39,7 +40,11 @@ def get_project_member(slug: str, project: str, user_id: str):
|
||||
@strawberry.type
|
||||
class WorkspacePageMutation:
|
||||
@strawberry.mutation(
|
||||
extensions=[PermissionExtension(permissions=[WorkspaceBasePermission()])]
|
||||
extensions=[
|
||||
PermissionExtension(
|
||||
permissions=[WorkspacePermission(roles=[Roles.ADMIN, Roles.MEMBER])]
|
||||
)
|
||||
]
|
||||
)
|
||||
async def updateWorkspacePage(
|
||||
self,
|
||||
|
||||
@@ -1,35 +1,38 @@
|
||||
# Strawberry imports
|
||||
import strawberry
|
||||
from strawberry.types import Info
|
||||
from strawberry.scalars import JSON
|
||||
from strawberry.permission import PermissionExtension
|
||||
from strawberry.exceptions import GraphQLError
|
||||
|
||||
# Third-party imports
|
||||
from asgiref.sync import sync_to_async
|
||||
# Python imports
|
||||
from typing import Optional
|
||||
|
||||
# Third-party imports
|
||||
import strawberry
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
# Strawberry imports
|
||||
from strawberry.exceptions import GraphQLError
|
||||
from strawberry.permission import PermissionExtension
|
||||
from strawberry.scalars import JSON
|
||||
from strawberry.types import Info
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import (
|
||||
IssueUserProperty,
|
||||
Project,
|
||||
ProjectMember,
|
||||
State,
|
||||
UserFavorite,
|
||||
Workspace,
|
||||
WorkspaceMember,
|
||||
)
|
||||
from plane.ee.models import ProjectFeature
|
||||
from plane.graphql.permissions.project import (
|
||||
ProjectAdminPermission,
|
||||
ProjectBasePermission,
|
||||
ProjectMemberPermission,
|
||||
)
|
||||
from plane.graphql.permissions.workspace import (
|
||||
WorkspaceBasePermission,
|
||||
WorkspacePermission,
|
||||
)
|
||||
from plane.graphql.permissions.project import (
|
||||
ProjectMemberPermission,
|
||||
ProjectBasePermission,
|
||||
ProjectAdminPermission,
|
||||
)
|
||||
from plane.graphql.types.project import ProjectType
|
||||
from plane.graphql.utils.roles import Roles
|
||||
from plane.db.models import (
|
||||
Workspace,
|
||||
WorkspaceMember,
|
||||
Project,
|
||||
ProjectMember,
|
||||
UserFavorite,
|
||||
IssueUserProperty,
|
||||
State,
|
||||
)
|
||||
|
||||
|
||||
@strawberry.type
|
||||
@@ -52,9 +55,13 @@ class ProjectMutation:
|
||||
cover_image: Optional[str] = None,
|
||||
project_lead: Optional[strawberry.ID] = None,
|
||||
logo_props: Optional[JSON] = {},
|
||||
page_view: Optional[bool] = False,
|
||||
page_view: Optional[bool] = True,
|
||||
module_view: Optional[bool] = True,
|
||||
cycle_view: Optional[bool] = True,
|
||||
issue_views_view: Optional[bool] = True,
|
||||
) -> ProjectType:
|
||||
workspace = await sync_to_async(Workspace.objects.get)(slug=slug)
|
||||
|
||||
try:
|
||||
project = await sync_to_async(Project.objects.create)(
|
||||
name=name,
|
||||
@@ -64,6 +71,9 @@ class ProjectMutation:
|
||||
logo_props=logo_props,
|
||||
cover_image=cover_image,
|
||||
page_view=page_view,
|
||||
module_view=module_view,
|
||||
cycle_view=cycle_view,
|
||||
issue_views_view=issue_views_view,
|
||||
workspace=workspace,
|
||||
)
|
||||
except Exception:
|
||||
@@ -143,6 +153,11 @@ class ProjectMutation:
|
||||
]
|
||||
)
|
||||
|
||||
# Project feature
|
||||
_ = await sync_to_async(ProjectFeature.objects.create)(
|
||||
workspace_id=workspace.id, project_id=project.id
|
||||
)
|
||||
|
||||
return project
|
||||
|
||||
@strawberry.mutation(
|
||||
@@ -158,6 +173,9 @@ class ProjectMutation:
|
||||
network: Optional[int] = None,
|
||||
logo_props: Optional[JSON] = None,
|
||||
page_view: Optional[bool] = None,
|
||||
module_view: Optional[bool] = None,
|
||||
cycle_view: Optional[bool] = None,
|
||||
issue_views_view: Optional[bool] = None,
|
||||
cover_image: Optional[str] = None,
|
||||
) -> ProjectType:
|
||||
project = await sync_to_async(Project.objects.get)(id=id)
|
||||
@@ -173,8 +191,12 @@ class ProjectMutation:
|
||||
project.logo_props = logo_props
|
||||
if page_view is not None:
|
||||
project.page_view = page_view
|
||||
if page_view is not None:
|
||||
project.page_view = page_view
|
||||
if module_view is not None:
|
||||
project.module_view = module_view
|
||||
if cycle_view is not None:
|
||||
project.cycle_view = cycle_view
|
||||
if issue_views_view is not None:
|
||||
project.issue_views_view = issue_views_view
|
||||
if cover_image is not None:
|
||||
project.cover_image = cover_image
|
||||
await sync_to_async(project.save)()
|
||||
|
||||
@@ -9,6 +9,7 @@ from strawberry.types import Info
|
||||
|
||||
# Module Imports
|
||||
from plane.db.models import Issue, Project, ProjectMember
|
||||
from plane.graphql.helpers.teamspace import project_member_filter_via_teamspaces_async
|
||||
from plane.graphql.permissions.workspace import WorkspacePermission
|
||||
from plane.graphql.types.issues.meta import IssueShortenedMetaInfo
|
||||
from plane.graphql.utils.roles import Roles
|
||||
@@ -72,6 +73,9 @@ class IssueShortenedMetaInfoQuery:
|
||||
async def issue_shortened_meta_info(
|
||||
self, info: Info, slug: str, work_item_identifier: str
|
||||
) -> IssueShortenedMetaInfo:
|
||||
user = info.context.user
|
||||
user_id = str(user.id)
|
||||
|
||||
workspace_slug = slug
|
||||
project_identifier = None
|
||||
issue_sequence = None
|
||||
@@ -87,15 +91,25 @@ class IssueShortenedMetaInfoQuery:
|
||||
error_extensions = {"code": "ISSUE_NOT_FOUND", "statusCode": 404}
|
||||
raise GraphQLError(message, extensions=error_extensions)
|
||||
|
||||
project_id = await get_project_id(workspace_slug, project_identifier)
|
||||
|
||||
is_project_member = await project_member_exists(
|
||||
workspace_slug, project_identifier, info.context.user
|
||||
)
|
||||
if not is_project_member:
|
||||
message = "User does not have permission to access this project."
|
||||
error_extensions = {"code": "UNAUTHORIZED", "statusCode": 403}
|
||||
raise GraphQLError(message, extensions=error_extensions)
|
||||
|
||||
project_id = await get_project_id(workspace_slug, project_identifier)
|
||||
# validate teamspace membership
|
||||
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 (
|
||||
not teamspace_project_ids
|
||||
or str(project_id) not in teamspace_project_ids
|
||||
):
|
||||
message = "User does not have permission to access this project."
|
||||
error_extensions = {"code": "UNAUTHORIZED", "statusCode": 403}
|
||||
raise GraphQLError(message, extensions=error_extensions)
|
||||
|
||||
issue_id, is_epic = await get_issue_id(
|
||||
workspace_slug, project_id, issue_sequence
|
||||
|
||||
@@ -64,16 +64,17 @@ class PageQuery:
|
||||
)
|
||||
|
||||
# Get shared page ids for private and shared pages
|
||||
if type in ["private", "shared"]:
|
||||
shared_pages = await sync_to_async(list)(
|
||||
PageUser.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project__id=project,
|
||||
).values_list("page_id", flat=True)
|
||||
shared_pages_data = await sync_to_async(list)(
|
||||
PageUser.objects.filter(workspace__slug=slug, project__id=project).values(
|
||||
"page_id", "user_id"
|
||||
)
|
||||
shared_page_ids = list(shared_pages)
|
||||
else:
|
||||
shared_page_ids = []
|
||||
)
|
||||
shared_page_ids = list(set(str(item["page_id"]) for item in shared_pages_data))
|
||||
pages_shared_with_user = [
|
||||
str(item["page_id"])
|
||||
for item in shared_pages_data
|
||||
if str(item["user_id"]) == user_id
|
||||
]
|
||||
|
||||
# Filter archived pages for all, public and shared pages
|
||||
if type != "archived":
|
||||
@@ -90,11 +91,15 @@ class PageQuery:
|
||||
page_base_query = page_base_query.filter(access=0)
|
||||
elif type == "private":
|
||||
page_base_query = page_base_query.filter(
|
||||
Q(access=1) & ~Q(id__in=shared_page_ids)
|
||||
).filter(owned_by_id=user_id)
|
||||
access=1, owned_by_id=user_id
|
||||
).exclude(id__in=shared_page_ids)
|
||||
elif type == "shared":
|
||||
page_base_query = page_base_query.filter(
|
||||
Q(access=1) & Q(id__in=shared_page_ids)
|
||||
Q(access=1)
|
||||
& (
|
||||
Q(id__in=pages_shared_with_user)
|
||||
| (Q(owned_by_id=user_id) & Q(id__in=shared_page_ids))
|
||||
)
|
||||
)
|
||||
|
||||
# Subquery for UserFavorite
|
||||
|
||||
@@ -58,17 +58,18 @@ class UserPageQuery:
|
||||
.filter(project_teamspace_filter_query)
|
||||
)
|
||||
|
||||
# Get shared page ids
|
||||
if type in ["private", "shared"]:
|
||||
shared_pages = await sync_to_async(list)(
|
||||
PageUser.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id__isnull=False,
|
||||
).values_list("page_id", flat=True)
|
||||
)
|
||||
shared_page_ids = list(shared_pages)
|
||||
else:
|
||||
shared_page_ids = []
|
||||
shared_pages_data = await sync_to_async(list)(
|
||||
PageUser.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id__isnull=False,
|
||||
).values("page_id", "user_id")
|
||||
)
|
||||
shared_page_ids = list(set(str(item["page_id"]) for item in shared_pages_data))
|
||||
pages_shared_with_user = [
|
||||
str(item["page_id"])
|
||||
for item in shared_pages_data
|
||||
if str(item["user_id"]) == user_id
|
||||
]
|
||||
|
||||
# Filter archived pages
|
||||
if type != "archived":
|
||||
@@ -85,11 +86,15 @@ class UserPageQuery:
|
||||
page_base_query = page_base_query.filter(access=0)
|
||||
elif type == "private":
|
||||
page_base_query = page_base_query.filter(
|
||||
Q(access=1) & ~Q(id__in=shared_page_ids)
|
||||
).filter(owned_by_id=user_id)
|
||||
access=1, owned_by_id=user_id
|
||||
).exclude(id__in=shared_page_ids)
|
||||
elif type == "shared":
|
||||
page_base_query = page_base_query.filter(
|
||||
Q(access=1) & Q(id__in=shared_page_ids)
|
||||
Q(access=1)
|
||||
& (
|
||||
Q(id__in=pages_shared_with_user)
|
||||
| (Q(owned_by_id=user_id) & Q(id__in=shared_page_ids))
|
||||
)
|
||||
)
|
||||
|
||||
subquery = UserFavorite.objects.filter(
|
||||
|
||||
@@ -25,13 +25,18 @@ from plane.graphql.types.page import PageType
|
||||
from plane.graphql.types.paginator import PaginatorResponse
|
||||
from plane.graphql.utils.feature_flag import validate_feature_flag
|
||||
from plane.graphql.utils.paginator import paginate
|
||||
from plane.graphql.utils.roles import Roles
|
||||
|
||||
|
||||
# workspace level queries
|
||||
@strawberry.type
|
||||
class WorkspacePageQuery:
|
||||
@strawberry.field(
|
||||
extensions=[PermissionExtension(permissions=[WorkspacePermission()])]
|
||||
extensions=[
|
||||
PermissionExtension(
|
||||
permissions=[WorkspacePermission(roles=[Roles.ADMIN, Roles.MEMBER])]
|
||||
)
|
||||
]
|
||||
)
|
||||
async def workspacePages(
|
||||
self,
|
||||
@@ -61,16 +66,15 @@ class WorkspacePageQuery:
|
||||
.filter(moved_to_page__isnull=True)
|
||||
)
|
||||
|
||||
# Get shared page ids
|
||||
if type in ["private", "shared"]:
|
||||
shared_pages = await sync_to_async(list)(
|
||||
PageUser.objects.filter(
|
||||
workspace__slug=slug,
|
||||
).values_list("page_id", flat=True)
|
||||
)
|
||||
shared_page_ids = list(shared_pages)
|
||||
else:
|
||||
shared_page_ids = []
|
||||
shared_pages_data = await sync_to_async(list)(
|
||||
PageUser.objects.filter(workspace__slug=slug).values("page_id", "user_id")
|
||||
)
|
||||
shared_page_ids = list(set(str(item["page_id"]) for item in shared_pages_data))
|
||||
pages_shared_with_user = [
|
||||
str(item["page_id"])
|
||||
for item in shared_pages_data
|
||||
if str(item["user_id"]) == user_id
|
||||
]
|
||||
|
||||
# Filter archived pages
|
||||
if type != "archived":
|
||||
@@ -87,11 +91,15 @@ class WorkspacePageQuery:
|
||||
page_base_query = page_base_query.filter(access=0)
|
||||
elif type == "private":
|
||||
page_base_query = page_base_query.filter(
|
||||
Q(access=1) & ~Q(id__in=shared_page_ids)
|
||||
).filter(owned_by_id=user_id)
|
||||
access=1, owned_by_id=user_id
|
||||
).exclude(id__in=shared_page_ids)
|
||||
elif type == "shared":
|
||||
page_base_query = page_base_query.filter(
|
||||
Q(access=1) & Q(id__in=shared_page_ids)
|
||||
Q(access=1)
|
||||
& (
|
||||
Q(id__in=pages_shared_with_user)
|
||||
| (Q(owned_by_id=user_id) & Q(id__in=shared_page_ids))
|
||||
)
|
||||
)
|
||||
|
||||
# Build subquery for UserFavorite
|
||||
@@ -103,9 +111,10 @@ class WorkspacePageQuery:
|
||||
)
|
||||
|
||||
pages = await sync_to_async(list)(
|
||||
page_base_query.select_related("workspace", "owned_by")
|
||||
page_base_query.distinct()
|
||||
.select_related("workspace", "owned_by")
|
||||
.prefetch_related("projects")
|
||||
.annotate(is_favorite=Exists(subquery))
|
||||
.order_by("-created_at")
|
||||
)
|
||||
|
||||
return paginate(results_object=pages, cursor=cursor)
|
||||
@@ -113,7 +122,10 @@ class WorkspacePageQuery:
|
||||
@strawberry.field(
|
||||
extensions=[
|
||||
PermissionExtension(
|
||||
permissions=[WorkspacePermission(), WorkspaceSharedPagePermission()]
|
||||
permissions=[
|
||||
WorkspacePermission(roles=[Roles.ADMIN, Roles.MEMBER]),
|
||||
WorkspaceSharedPagePermission(),
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
@@ -5,10 +5,11 @@ from typing import Optional
|
||||
# Third-party library imports
|
||||
import strawberry
|
||||
import strawberry_django
|
||||
from asgiref.sync import sync_to_async
|
||||
|
||||
# Strawberry 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 Page
|
||||
@@ -17,6 +18,25 @@ from plane.graphql.types.project import ProjectLiteType
|
||||
from plane.graphql.utils.timezone import user_timezone_converter
|
||||
|
||||
|
||||
@sync_to_async
|
||||
def shared_page_access(user_id: str, workspace_slug: str, page_id: str, owner_id: str):
|
||||
shared_pages_data = PageUser.objects.filter(
|
||||
workspace__slug=workspace_slug, page_id=page_id
|
||||
).values("user_id", "access")
|
||||
shared_pages_data = list(shared_pages_data)
|
||||
|
||||
page_access = 0
|
||||
if owner_id == user_id:
|
||||
page_access = 2
|
||||
else:
|
||||
for item in shared_pages_data:
|
||||
if str(item["user_id"]) == user_id:
|
||||
page_access = item["access"]
|
||||
break
|
||||
|
||||
return (len(shared_pages_data) > 0, page_access)
|
||||
|
||||
|
||||
@strawberry_django.type(Page)
|
||||
class PageType:
|
||||
id: strawberry.ID
|
||||
@@ -79,38 +99,46 @@ class PageType:
|
||||
return converted_date
|
||||
|
||||
@strawberry.field
|
||||
async def is_shared(self) -> bool:
|
||||
async def is_shared(self, info: Info) -> bool:
|
||||
if self.access == 0:
|
||||
return False
|
||||
|
||||
user = info.context.user
|
||||
user_id = str(user.id)
|
||||
|
||||
workspace_slug = self.workspace.slug
|
||||
page_id = self.id
|
||||
shared_page = await sync_to_async(
|
||||
lambda: PageUser.objects.filter(
|
||||
workspace__slug=workspace_slug,
|
||||
page_id=page_id,
|
||||
).first()
|
||||
)()
|
||||
if shared_page:
|
||||
page_id = str(self.id)
|
||||
|
||||
shared_page_count, _ = await shared_page_access(
|
||||
workspace_slug=workspace_slug,
|
||||
page_id=page_id,
|
||||
user_id=user_id,
|
||||
owner_id=str(self.owned_by_id),
|
||||
)
|
||||
if shared_page_count > 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
@strawberry.field
|
||||
async def is_shared_access(self) -> int:
|
||||
async def is_shared_access(self, info: Info) -> int:
|
||||
if self.access == 0:
|
||||
return 0
|
||||
|
||||
user = info.context.user
|
||||
user_id = str(user.id)
|
||||
|
||||
workspace_slug = self.workspace.slug
|
||||
page_id = self.id
|
||||
shared_page = await sync_to_async(
|
||||
lambda: PageUser.objects.filter(
|
||||
workspace__slug=workspace_slug,
|
||||
page_id=page_id,
|
||||
).first()
|
||||
)()
|
||||
if shared_page:
|
||||
shared_page_access = shared_page.access
|
||||
return shared_page_access
|
||||
page_id = str(self.id)
|
||||
|
||||
shared_page_count, page_access = await shared_page_access(
|
||||
workspace_slug=workspace_slug,
|
||||
page_id=page_id,
|
||||
user_id=user_id,
|
||||
owner_id=str(self.owned_by_id),
|
||||
)
|
||||
|
||||
if shared_page_count > 0:
|
||||
return page_access
|
||||
return 0
|
||||
|
||||
# @strawberry.field
|
||||
|
||||
Reference in New Issue
Block a user