Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: performance booster optimization #176

Merged
merged 24 commits into from
Jan 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
d710272
refactor: setup multiple select related
pablohashescobar Jan 13, 2023
70e78af
chore: upgrade sentry sdk to latest version
pablohashescobar Jan 13, 2023
e5e4c5e
refactor: update module and cycle views to increase performance
pablohashescobar Jan 13, 2023
f73cb09
refactor: remove pagination and make the response simillar to paginat…
pablohashescobar Jan 14, 2023
2095e4b
fix: update staging to DEBUG True for all logging
pablohashescobar Jan 14, 2023
95ef68c
refactor: update the query count print statement
pablohashescobar Jan 14, 2023
8b9f084
refactor: my issues endpoint to remove n+1
pablohashescobar Jan 14, 2023
dffce74
refactor: optimize queries for workspace and project
pablohashescobar Jan 14, 2023
41f6faf
fix: project member endpoint
pablohashescobar Jan 14, 2023
bbd0737
fix: revert back workspace members
pablohashescobar Jan 14, 2023
f5c220f
refactor: update base file to remove workspace and project query and …
pablohashescobar Jan 14, 2023
a217b05
refactor: update read_only fields in read serializers
pablohashescobar Jan 14, 2023
06d0f6f
fix: read only serializers
pablohashescobar Jan 14, 2023
d10e4b3
chore: update drf package
pablohashescobar Jan 14, 2023
3e34e56
revert: drf version upgrade
pablohashescobar Jan 14, 2023
528ab1e
revert: read only fields update
pablohashescobar Jan 14, 2023
933f1bb
revert: update serializer to old state
pablohashescobar Jan 14, 2023
810efb5
chore: update drf to latest version
pablohashescobar Jan 14, 2023
f6467c5
refactor: update dispatch to display method as well
pablohashescobar Jan 14, 2023
2f256ee
refactor: optimize cycle and module issue queries
pablohashescobar Jan 15, 2023
1360db9
refactor: optimize module endpoint and issue list endpoint
pablohashescobar Jan 15, 2023
c07ea93
refactor: update prefetch related in modules and cycles
pablohashescobar Jan 15, 2023
a589cdf
Merge branch 'develop' of github.com:makeplane/plane into refactor/pe…
pablohashescobar Jan 16, 2023
13405c9
refactor: create permission mapping in permission file
pablohashescobar Jan 16, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 11 additions & 5 deletions apiserver/plane/api/permissions/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@
# Module import
from plane.db.models import WorkspaceMember, ProjectMember

# Permission Mappings
Admin = 20
Member = 15
Viewer = 10
Guest = 5


class ProjectBasePermission(BasePermission):
def has_permission(self, request, view):
Expand All @@ -22,14 +28,14 @@ def has_permission(self, request, view):
return WorkspaceMember.objects.filter(
workspace__slug=view.workspace_slug,
member=request.user,
role__in=[15, 20],
role__in=[Admin, Member],
).exists()

## Only Project Admins can update project attributes
return ProjectMember.objects.filter(
workspace__slug=view.workspace_slug,
member=request.user,
role=20,
role=Admin,
project_id=view.project_id,
).exists()

Expand All @@ -50,14 +56,14 @@ def has_permission(self, request, view):
return WorkspaceMember.objects.filter(
workspace__slug=view.workspace_slug,
member=request.user,
role__in=[15, 20],
role__in=[Admin, Member],
).exists()

## Only Project Admins can update project attributes
return ProjectMember.objects.filter(
workspace__slug=view.workspace_slug,
member=request.user,
role__in=[15, 20],
role__in=[Admin, Member],
project_id=view.project_id,
).exists()

Expand All @@ -80,6 +86,6 @@ def has_permission(self, request, view):
return ProjectMember.objects.filter(
workspace__slug=view.workspace_slug,
member=request.user,
role__in=[15, 20],
role__in=[Admin, Member],
project_id=view.project_id,
).exists()
20 changes: 16 additions & 4 deletions apiserver/plane/api/permissions/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,15 @@
from rest_framework.permissions import BasePermission, SAFE_METHODS

# Module imports
from plane.db.models import WorkspaceMember, ProjectMember
from plane.db.models import WorkspaceMember



# Permission Mappings
Owner = 20
Admin = 15
Member = 10
Guest = 5


# TODO: Move the below logic to python match - python v3.10
Expand All @@ -22,13 +30,15 @@ def has_permission(self, request, view):
# allow only admins and owners to update the workspace settings
if request.method in ["PUT", "PATCH"]:
return WorkspaceMember.objects.filter(
member=request.user, workspace=view.workspace, role__in=[15, 20]
member=request.user,
workspace__slug=view.workspace_slug,
role__in=[Owner, Admin],
).exists()

# allow only owner to delete the workspace
if request.method == "DELETE":
return WorkspaceMember.objects.filter(
member=request.user, workspace=view.workspace, role=20
member=request.user, workspace__slug=view.workspace_slug, role=Owner
).exists()


Expand All @@ -39,5 +49,7 @@ def has_permission(self, request, view):
return False

return WorkspaceMember.objects.filter(
member=request.user, workspace=view.workspace, role__in=[15, 20]
member=request.user,
workspace__slug=view.workspace_slug,
role__in=[Owner, Admin],
).exists()
1 change: 0 additions & 1 deletion apiserver/plane/api/serializers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
IssueCommentSerializer,
TimeLineIssueSerializer,
IssuePropertySerializer,
IssueLabelSerializer,
BlockerIssueSerializer,
BlockedIssueSerializer,
IssueAssigneeSerializer,
Expand Down
2 changes: 1 addition & 1 deletion apiserver/plane/api/serializers/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -443,4 +443,4 @@ class Meta:
"updated_by",
"created_at",
"updated_at",
]
]
55 changes: 10 additions & 45 deletions apiserver/plane/api/views/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Django imports
from django.urls import resolve
from django.conf import settings

# Third part imports
from rest_framework import status
from rest_framework.viewsets import ModelViewSet
Expand Down Expand Up @@ -39,32 +40,23 @@ def get_queryset(self):
return self.model.objects.all()
except Exception as e:
print(e)
raise APIException(
"Please check the view", status.HTTP_400_BAD_REQUEST
)
raise APIException("Please check the view", status.HTTP_400_BAD_REQUEST)

def dispatch(self, request, *args, **kwargs):
response = super().dispatch(request, *args, **kwargs)

if settings.DEBUG:
from django.db import connection
print(f'# of Queries: {len(connection.queries)}')

print(
f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}"
)
return response

@property
def workspace_slug(self):
return self.kwargs.get("slug", None)

@property
def workspace(self):
if self.workspace_slug:
try:
return Workspace.objects.get(slug=self.workspace_slug)
except Workspace.DoesNotExist:
raise NotFound(detail="Workspace does not exist")
else:
return None

@property
def project_id(self):
project_id = self.kwargs.get("project_id", None)
Expand All @@ -74,16 +66,6 @@ def project_id(self):
if resolve(self.request.path_info).url_name == "project":
return self.kwargs.get("pk", None)

@property
def project(self):
if self.project_id:
try:
return Project.objects.get(pk=self.project_id)
except Project.DoesNotExist:
raise NotFound(detail="Project does not exist")
else:
return None


class BaseAPIView(APIView, BasePaginator):

Expand All @@ -110,33 +92,16 @@ def dispatch(self, request, *args, **kwargs):

if settings.DEBUG:
from django.db import connection
print(f'# of Queries: {len(connection.queries)}')

print(
f"{request.method} - {request.get_full_path()} of Queries: {len(connection.queries)}"
)
return response

@property
def workspace_slug(self):
return self.kwargs.get("slug", None)

@property
def workspace(self):
if self.workspace_slug:
try:
return Workspace.objects.get(slug=self.workspace_slug)
except Workspace.DoesNotExist:
raise NotFound(detail="Workspace does not exist")
else:
return None

@property
def project_id(self):
return self.kwargs.get("project_id", None)

@property
def project(self):
if self.project_id:
try:
return Project.objects.get(pk=self.project_id)
except Project.DoesNotExist:
raise NotFound(detail="Project does not exist")
else:
return None
5 changes: 3 additions & 2 deletions apiserver/plane/api/views/cycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def get_queryset(self):
.filter(project__project_projectmember__member=self.request.user)
.select_related("project")
.select_related("workspace")
.select_related("owned_by")
.distinct()
)

Expand Down Expand Up @@ -62,8 +63,8 @@ def get_queryset(self):
.select_related("project")
.select_related("workspace")
.select_related("cycle")
.select_related("issue")
.select_related("issue__state")
.select_related("issue", "issue__state", "issue__project")
.prefetch_related("issue__assignees", "issue__labels")
.distinct()
)

Expand Down
58 changes: 51 additions & 7 deletions apiserver/plane/api/views/issue.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ def get_queryset(self):
.prefetch_related(
Prefetch(
"issue_module",
queryset=ModuleIssue.objects.select_related("module", "issue"),
queryset=ModuleIssue.objects.select_related(
"module", "issue"
).prefetch_related("module__members"),
),
)
)
Expand Down Expand Up @@ -161,10 +163,18 @@ def list(self, request, slug, project_id):

return Response(issue_dict, status=status.HTTP_200_OK)

return self.paginate(
request=request,
queryset=issue_queryset,
on_results=lambda issues: IssueSerializer(issues, many=True).data,
return Response(
{
"next_cursor": str(0),
"prev_cursor": str(0),
"next_page_results": False,
"prev_page_results": False,
"count": issue_queryset.count(),
"total_pages": 1,
"extra_stats": {},
"results": IssueSerializer(issue_queryset, many=True).data,
},
status=status.HTTP_200_OK,
)

except Exception as e:
Expand Down Expand Up @@ -206,8 +216,42 @@ def create(self, request, slug, project_id):
class UserWorkSpaceIssues(BaseAPIView):
def get(self, request, slug):
try:
issues = Issue.objects.filter(
assignees__in=[request.user], workspace__slug=slug
issues = (
Issue.objects.filter(assignees__in=[request.user], workspace__slug=slug)
.select_related("project")
.select_related("workspace")
.select_related("state")
.select_related("parent")
.prefetch_related("assignees")
.prefetch_related("labels")
.prefetch_related(
Prefetch(
"blocked_issues",
queryset=IssueBlocker.objects.select_related(
"blocked_by", "block"
),
)
)
.prefetch_related(
Prefetch(
"blocker_issues",
queryset=IssueBlocker.objects.select_related(
"block", "blocked_by"
),
)
)
.prefetch_related(
Prefetch(
"issue_cycle",
queryset=CycleIssue.objects.select_related("cycle", "issue"),
),
)
.prefetch_related(
Prefetch(
"issue_module",
queryset=ModuleIssue.objects.select_related("module", "issue"),
),
)
)
serializer = IssueSerializer(issues, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
Expand Down
20 changes: 15 additions & 5 deletions apiserver/plane/api/views/module.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@
ModuleIssueSerializer,
)
from plane.api.permissions import ProjectEntityPermission
from plane.db.models import Module, ModuleIssue, Project, Issue, ModuleLink
from plane.db.models import (
Module,
ModuleIssue,
Project,
Issue,
ModuleLink,
)


class ModuleViewSet(BaseViewSet):
Expand Down Expand Up @@ -45,13 +51,15 @@ def get_queryset(self):
.prefetch_related(
Prefetch(
"issue_module",
queryset=ModuleIssue.objects.select_related("module", "issue"),
queryset=ModuleIssue.objects.select_related(
"module", "issue", "issue__state", "issue__project"
).prefetch_related("issue__assignees", "issue__labels"),
)
)
.prefetch_related(
Prefetch(
"link_module",
queryset=ModuleLink.objects.select_related("module"),
queryset=ModuleLink.objects.select_related("module", "created_by"),
)
)
)
Expand Down Expand Up @@ -117,7 +125,9 @@ def get_queryset(self):
.select_related("project")
.select_related("workspace")
.select_related("module")
.select_related("issue")
.select_related("issue", "issue__state", "issue__project")
.prefetch_related("issue__assignees", "issue__labels")
.prefetch_related("module__members")
.distinct()
)

Expand Down Expand Up @@ -164,4 +174,4 @@ def create(self, request, slug, project_id, module_id):
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
)
Loading