[MOBIL-167] chore: handle status code in error messages, updated key in issue global search, and added project identifier in IssueType (#1075)

* dev: updated project details

* chore: udated version strawberry-graphql-django

* chore: handled error messages and status code in error extensions

* chore: resolved import error

* chore: added project_identifier in issue_type

* chore: project_identifier in issues global search

* chore: handled error_extensions
This commit is contained in:
guru_sainath
2024-09-09 20:22:59 +05:30
committed by GitHub
parent 879fa7ee73
commit 4acaba96ba
10 changed files with 108 additions and 11 deletions

View File

@@ -12,6 +12,7 @@ from strawberry.types import Info
from strawberry.permission import BasePermission
# Local Imports
from plane.graphql.utils.error_codes import ERROR_CODES
from plane.db.models import ProjectMember
# Permission Mappings
@@ -23,6 +24,10 @@ Guest = 5
class IsAuthenticated(BasePermission):
message = "User is not authenticated"
error_extensions = {
"code": "UNAUTHENTICATED",
"statusCode": ERROR_CODES["USER_NOT_AUTHENTICATED"],
}
async def has_permission(self, source: Any, info: Info, **kwargs) -> bool:
self.user = await sync_to_async(get_user, thread_sensitive=True)(
@@ -33,11 +38,16 @@ class IsAuthenticated(BasePermission):
class ProjectBasePermission(IsAuthenticated):
message = "User does not have permission to access this project"
error_extensions = {
"code": "UNAUTHORIZED",
"statusCode": ERROR_CODES["USER_NOT_AUTHORIZED"],
}
async def has_permission(self, source: Any, info: Info, **kwargs) -> bool:
# First, check if the user is authenticated by calling the parent class's method
if not await super().has_permission(source, info, **kwargs):
self.message = IsAuthenticated.message
self.error_extensions = IsAuthenticated.error_extensions
return False
return await sync_to_async(
@@ -52,13 +62,17 @@ class ProjectBasePermission(IsAuthenticated):
class ProjectMemberPermission(IsAuthenticated):
message = "Only project admins or members can perform this action"
error_extensions = {
"code": "UNAUTHORIZED",
"statusCode": ERROR_CODES["USER_NOT_AUTHORIZED"],
}
async def has_permission(self, source: Any, info: Info, **kwargs) -> bool:
# First, check if the user is authenticated by calling the parent class's method
if not await super().has_permission(source, info, **kwargs):
self.message = IsAuthenticated.message
self.error_extensions = IsAuthenticated.error_extensions
return False
return await sync_to_async(
@@ -74,13 +88,17 @@ class ProjectMemberPermission(IsAuthenticated):
class ProjectAdminPermission(IsAuthenticated):
message = "Only admins can perform this action"
error_extensions = {
"code": "UNAUTHORIZED",
"statusCode": ERROR_CODES["USER_NOT_AUTHORIZED"],
}
async def has_permission(self, source: Any, info: Info, **kwargs) -> bool:
# First, check if the user is authenticated by calling the parent class's method
if not await super().has_permission(source, info, **kwargs):
self.message = IsAuthenticated.message
self.error_extensions = IsAuthenticated.error_extensions
return False
return await sync_to_async(

View File

@@ -12,6 +12,7 @@ from strawberry.types import Info
from strawberry.permission import BasePermission
# Local Imports
from plane.graphql.utils.error_codes import ERROR_CODES
from plane.db.models import WorkspaceMember
# Permission Mappings
@@ -23,6 +24,10 @@ Guest = 5
class IsAuthenticated(BasePermission):
message = "User is not authenticated"
error_extensions = {
"code": "UNAUTHENTICATED",
"statusCode": ERROR_CODES["USER_NOT_AUTHENTICATED"],
}
async def has_permission(self, source: Any, info: Info, **kwargs) -> bool:
self.user = await sync_to_async(get_user, thread_sensitive=True)(
@@ -33,11 +38,16 @@ class IsAuthenticated(BasePermission):
class WorkspaceBasePermission(IsAuthenticated):
message = "User does not have permission to access this workspace"
error_extensions = {
"code": "UNAUTHORIZED",
"statusCode": ERROR_CODES["USER_NOT_AUTHORIZED"],
}
async def has_permission(self, source: Any, info: Info, **kwargs) -> bool:
# First, check if the user is authenticated by calling the parent class's method
if not await super().has_permission(source, info, **kwargs):
self.message = IsAuthenticated.message
self.error_extensions = IsAuthenticated.error_extensions
return False
return await sync_to_async(
@@ -51,13 +61,17 @@ class WorkspaceBasePermission(IsAuthenticated):
class WorkspaceMemberPermission(IsAuthenticated):
message = "Workspace admins or members can perform this action"
error_extensions = {
"code": "UNAUTHORIZED",
"statusCode": ERROR_CODES["USER_NOT_AUTHORIZED"],
}
async def has_permission(self, source: Any, info: Info, **kwargs) -> bool:
# First, check if the user is authenticated by calling the parent class's method
if not await super().has_permission(source, info, **kwargs):
self.message = IsAuthenticated.message
self.error_extensions = IsAuthenticated.error_extensions
return False
return await sync_to_async(
@@ -73,11 +87,16 @@ class WorkspaceMemberPermission(IsAuthenticated):
class WorkspaceAdminPermission(IsAuthenticated):
message = "Only workspace admins can perform this action"
error_extensions = {
"code": "UNAUTHORIZED",
"statusCode": ERROR_CODES["USER_NOT_AUTHORIZED"],
}
async def has_permission(self, source: Any, info: Info, **kwargs) -> bool:
# First, check if the user is authenticated by calling the parent class's method
if not await super().has_permission(source, info, **kwargs):
self.message = IsAuthenticated.message
self.error_extensions = IsAuthenticated.error_extensions
return False
return await sync_to_async(

View File

@@ -173,7 +173,7 @@ class ProjectQuery:
.first()
)
project = await sync_to_async(get_project)()
project_detail = await sync_to_async(get_project)()
# Background task to update recent visited project
user_id = info.context.user.id
@@ -185,7 +185,7 @@ class ProjectQuery:
entity_identifier=project,
)
return project
return project_detail
@strawberry.type

View File

@@ -100,14 +100,19 @@ async def filter_issues(
)
.distinct()
.values(
"name",
"id",
"sequence_id",
"project__identifier",
"name",
"project",
"project__identifier",
)
)
)()
for issue in issues:
issue["project_identifier"] = issue["project__identifier"]
del issue["project__identifier"]
return [IssueLiteType(**issue) for issue in issues]

View File

@@ -68,6 +68,7 @@ class IssuesType:
cycle: Optional[strawberry.ID]
modules: Optional[list[strawberry.ID]]
type: Optional[strawberry.ID]
project_identifier: Optional[str]
@strawberry.field
def state(self) -> int:
@@ -130,6 +131,10 @@ class IssuesType:
# Return the module IDs as strings
return [str(module_id) for module_id in module_issues]
@strawberry.field
def project_identifier(self) -> Optional[str]:
return self.project.identifier
@strawberry_django.type(IssueUserProperty)
class IssueUserPropertyType:
@@ -236,7 +241,7 @@ class IssueLiteType:
sequence_id: int
workspace: strawberry.ID
project: strawberry.ID
project__identifier: str
project_identifier: Optional[str]
@strawberry.field
def workspace(self) -> int:

View File

@@ -1,5 +1,4 @@
# Third party imports
from asgiref.sync import sync_to_async
from typing import Optional
# Strawberry imports

View File

@@ -0,0 +1,13 @@
ERROR_CODES = {
# authentication error codes
"USER_NOT_AUTHENTICATED": 401,
"USER_NOT_AUTHORIZED": 403,
# issues
"INVALID_ARCHIVE_STATE_GROUP": 4091,
"INVALID_ISSUE_DATES": 4100,
"INVALID_ISSUE_START_DATE": 4101,
"INVALID_ISSUE_TARGET_DATE": 4102,
# pages
"PAGE_LOCKED": 4701,
"PAGE_ARCHIVED": 4702,
}

View File

@@ -0,0 +1,12 @@
# strawberry imports
from strawberry.exceptions import GraphQLError
class CustomGraphQLError(GraphQLError):
def __init__(self, message, code=None, field=None):
super().__init__(message)
self.code = code
self.field = field
def extensions(self):
return {"code": self.code, "field": self.field}

View File

@@ -1,11 +1,16 @@
# Django imports
from django.contrib.auth import get_user
from typing import Any, Dict, Optional
# Third-Party Imports
from asgiref.sync import sync_to_async
# Strawberry imports
from strawberry.django.views import AsyncGraphQLView
from strawberry.django.views import AsyncGraphQLView
from strawberry.types import ExecutionResult
from strawberry.types.execution import ExecutionContext
class CustomGraphQLView(AsyncGraphQLView):
async def get_context(self, request, response):
@@ -16,3 +21,24 @@ class CustomGraphQLView(AsyncGraphQLView):
context = await super().get_context(request, response)
context.user = user
return context
async def process_result(
self,
request: Any,
result: ExecutionResult,
context: Optional[ExecutionContext] = None,
) -> Dict[str, Any]:
processed_result = {
"data": result.data,
}
if result.errors:
processed_result["errors"] = [
{
"message": error.message,
"extensions": error.extensions or {},
}
for error in result.errors
]
return processed_result

View File

@@ -69,4 +69,4 @@ python3-saml==1.16.0
# feature flag
openfeature-sdk==0.7.0
# graphql
strawberry-graphql-django==0.47.0
strawberry-graphql-django==0.47.2