From c33fc0e0ec3d96aa3197f1512a64f3c4006caac8 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 16 Jan 2023 23:23:55 +0530 Subject: [PATCH 01/10] fix: oauth key error --- apiserver/plane/api/views/oauth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apiserver/plane/api/views/oauth.py b/apiserver/plane/api/views/oauth.py index ac8b55c7e..bcebfb294 100644 --- a/apiserver/plane/api/views/oauth.py +++ b/apiserver/plane/api/views/oauth.py @@ -223,8 +223,8 @@ class OauthEndpoint(BaseAPIView): username=username, email=email, mobile_number=mobile_number, - first_name=data["first_name"], - last_name=data["last_name"], + first_name=data.get("first_name", ""), + last_name=data.get("last_name", ""), is_email_verified=email_verified, is_password_autoset=True, ) From f12b7ef923611884494ebf044f8cb074db067af3 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Tue, 17 Jan 2023 01:34:58 +0530 Subject: [PATCH 02/10] refactor: backend code cleanup (#177) * refactor: segregate urls in urls.py * refactor: remove all people endpoint * refactor: update file asset endpoint with slug and remove unused imports in issue * fix: remove people endpoint from __init__ * refactor: update permission logic to handle GET requests * feat: add url for sign up endpoint in urls * refactor: update the permission layer --- apiserver/plane/api/permissions/project.py | 42 +++++++--- apiserver/plane/api/urls.py | 90 +++++++++++++--------- apiserver/plane/api/views/__init__.py | 2 +- apiserver/plane/api/views/asset.py | 6 +- apiserver/plane/api/views/issue.py | 1 - apiserver/plane/api/views/people.py | 37 --------- 6 files changed, 88 insertions(+), 90 deletions(-) diff --git a/apiserver/plane/api/permissions/project.py b/apiserver/plane/api/permissions/project.py index 019496cda..44659a671 100644 --- a/apiserver/plane/api/permissions/project.py +++ b/apiserver/plane/api/permissions/project.py @@ -13,16 +13,24 @@ class ProjectBasePermission(BasePermission): ## Safe Methods -> Handle the filtering logic in queryset if request.method in SAFE_METHODS: - return True + return WorkspaceMember.objects.filter( + workspace__slug=view.workspace_slug, member=request.user + ).exists() + ## Only workspace owners or admins can create the projects if request.method == "POST": return WorkspaceMember.objects.filter( - workspace=view.workspace, member=request.user, role__in=[15, 20] + workspace__slug=view.workspace_slug, + member=request.user, + role__in=[15, 20], ).exists() ## Only Project Admins can update project attributes return ProjectMember.objects.filter( - workspace=view.workspace, member=request.user, role=20 + workspace__slug=view.workspace_slug, + member=request.user, + role=20, + project_id=view.project_id, ).exists() @@ -34,16 +42,23 @@ class ProjectMemberPermission(BasePermission): ## Safe Methods -> Handle the filtering logic in queryset if request.method in SAFE_METHODS: - return True + return ProjectMember.objects.filter( + workspace=view.workspace, member=request.user + ).exists() ## Only workspace owners or admins can create the projects if request.method == "POST": return WorkspaceMember.objects.filter( - workspace=view.workspace, member=request.user, role__in=[15, 20] + workspace__slug=view.workspace_slug, + member=request.user, + role__in=[15, 20], ).exists() ## Only Project Admins can update project attributes return ProjectMember.objects.filter( - workspace=view.workspace, member=request.user, role__in=[15, 20] + workspace__slug=view.workspace_slug, + member=request.user, + role__in=[15, 20], + project_id=view.project_id, ).exists() @@ -52,12 +67,19 @@ class ProjectEntityPermission(BasePermission): if request.user.is_anonymous: return False - + ## Safe Methods -> Handle the filtering logic in queryset if request.method in SAFE_METHODS: - return True - ## Only workspace owners or admins can create the projects + return ProjectMember.objects.filter( + workspace=view.workspace, + member=request.user, + project_id=view.project_id, + ).exists() + ## Only project members or admins can create and edit the project attributes return ProjectMember.objects.filter( - workspace=view.workspace, member=request.user, role__in=[15, 20] + workspace__slug=view.workspace_slug, + member=request.user, + role__in=[15, 20], + project_id=view.project_id, ).exists() diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index 872f5953f..050ce9c95 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -4,73 +4,94 @@ from django.urls import path # Create your urls here. from plane.api.views import ( + # Authentication + SignUpEndpoint, SignInEndpoint, SignOutEndpoint, MagicSignInEndpoint, MagicSignInGenerateEndpoint, + OauthEndpoint, + ## End Authentication + # Auth Extended ForgotPasswordEndpoint, - PeopleEndpoint, - UserEndpoint, VerifyEmailEndpoint, ResetPasswordEndpoint, RequestEmailVerificationEndpoint, - OauthEndpoint, ChangePasswordEndpoint, -) - -from plane.api.views import ( - UserWorkspaceInvitationsEndpoint, + ## End Auth Extender + # User + UserEndpoint, + UpdateUserOnBoardedEndpoint, + ## End User + # Workspaces WorkSpaceViewSet, + UserWorkspaceInvitationsEndpoint, UserWorkSpacesEndpoint, InviteWorkspaceEndpoint, JoinWorkspaceEndpoint, WorkSpaceMemberViewSet, WorkspaceInvitationsViewset, UserWorkspaceInvitationsEndpoint, + WorkspaceMemberUserEndpoint, + WorkspaceMemberUserViewsEndpoint, + WorkSpaceAvailabilityCheckEndpoint, + TeamMemberViewSet, + AddTeamToProjectEndpoint, + UserLastProjectWithWorkspaceEndpoint, + UserWorkspaceInvitationEndpoint, + ## End Workspaces + # File Assets + FileAssetEndpoint, + ## End File Assets + # Projects ProjectViewSet, InviteProjectEndpoint, ProjectMemberViewSet, ProjectMemberInvitationsViewset, - StateViewSet, - ShortCutViewSet, - ViewViewSet, - CycleViewSet, - FileAssetEndpoint, + ProjectMemberUserEndpoint, + AddMemberToProjectEndpoint, + ProjectJoinEndpoint, + UserProjectInvitationsViewset, + ProjectIdentifierEndpoint, + ## End Projects + # Issues IssueViewSet, WorkSpaceIssuesEndpoint, IssueActivityEndpoint, IssueCommentViewSet, - TeamMemberViewSet, - TimeLineIssueViewSet, - CycleIssueViewSet, - IssuePropertyViewSet, - UpdateUserOnBoardedEndpoint, - UserWorkspaceInvitationEndpoint, - UserProjectInvitationsViewset, - ProjectIdentifierEndpoint, - LabelViewSet, - AddMemberToProjectEndpoint, - ProjectJoinEndpoint, + UserWorkSpaceIssues, BulkDeleteIssuesEndpoint, ProjectUserViewsEndpoint, + TimeLineIssueViewSet, + IssuePropertyViewSet, + LabelViewSet, + ## End Issues + # States + StateViewSet, + ## End States + # Shortcuts + ShortCutViewSet, + ## End Shortcuts + # Views + ViewViewSet, + ## End Views + # Cycles + CycleViewSet, + CycleIssueViewSet, + ## End Cycles + # Modules ModuleViewSet, ModuleIssueViewSet, - UserLastProjectWithWorkspaceEndpoint, - UserWorkSpaceIssues, - ProjectMemberUserEndpoint, - WorkspaceMemberUserEndpoint, - WorkspaceMemberUserViewsEndpoint, - WorkSpaceAvailabilityCheckEndpoint, + ## End Modules ) -from plane.api.views.project import AddTeamToProjectEndpoint - urlpatterns = [ # Social Auth path("social-auth/", OauthEndpoint.as_view(), name="oauth"), # Auth path("sign-in/", SignInEndpoint.as_view(), name="sign-in"), + path("sign-up/", SignUpEndpoint.as_view(), name="sign-up"), path("sign-out/", SignOutEndpoint.as_view(), name="sign-out"), # Magic Sign In/Up path( @@ -95,8 +116,6 @@ urlpatterns = [ ForgotPasswordEndpoint.as_view(), name="forgot-password", ), - # List Users - path("users/", PeopleEndpoint.as_view()), # User Profile path( "users/me/", @@ -654,9 +673,4 @@ urlpatterns = [ name="project-module-issues", ), ## End Modules - # path( - # "issues//all/", - # IssueViewSet.as_view({"get": "list_issue_history_comments"}), - # name="Issue history and comments", - # ), ] diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 3a0193f8a..1cadd95e6 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -13,7 +13,6 @@ from .project import ( ProjectMemberUserEndpoint, ) from .people import ( - PeopleEndpoint, UserEndpoint, UpdateUserOnBoardedEndpoint, ) @@ -64,6 +63,7 @@ from .auth_extended import ( from .authentication import ( + SignUpEndpoint, SignInEndpoint, SignOutEndpoint, MagicSignInEndpoint, diff --git a/apiserver/plane/api/views/asset.py b/apiserver/plane/api/views/asset.py index 902ae7009..657383553 100644 --- a/apiserver/plane/api/views/asset.py +++ b/apiserver/plane/api/views/asset.py @@ -6,7 +6,7 @@ from sentry_sdk import capture_exception # Module imports from .base import BaseAPIView -from plane.db.models import FileAsset, Workspace +from plane.db.models import FileAsset from plane.api.serializers import FileAssetSerializer @@ -18,8 +18,8 @@ class FileAssetEndpoint(BaseAPIView): A viewset for viewing and editing task instances. """ - def get(self, request): - files = FileAsset.objects.all() + def get(self, request, slug): + files = FileAsset.objects.filter(workspace__slug=slug) serializer = FileAssetSerializer(files, context={"request": request}, many=True) return Response(serializer.data) diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 78f050af8..73ee40a83 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -4,7 +4,6 @@ from itertools import groupby, chain # Django imports from django.db.models import Prefetch -from django.db.models import Count, Sum from django.core.serializers.json import DjangoJSONEncoder # Third Party imports diff --git a/apiserver/plane/api/views/people.py b/apiserver/plane/api/views/people.py index b45176d1d..154888812 100644 --- a/apiserver/plane/api/views/people.py +++ b/apiserver/plane/api/views/people.py @@ -7,48 +7,11 @@ from sentry_sdk import capture_exception # Module imports from plane.api.serializers import ( UserSerializer, - WorkSpaceSerializer, ) from plane.api.views.base import BaseViewSet, BaseAPIView from plane.db.models import User, Workspace - -class PeopleEndpoint(BaseAPIView): - - filterset_fields = ("date_joined",) - - search_fields = ( - "^first_name", - "^last_name", - "^email", - "^username", - ) - - def get(self, request): - try: - users = User.objects.all().order_by("-date_joined") - if ( - request.GET.get("search", None) is not None - and len(request.GET.get("search")) < 3 - ): - return Response( - {"message": "Search term must be at least 3 characters long"}, - status=status.HTTP_400_BAD_REQUEST, - ) - return self.paginate( - request=request, - queryset=self.filter_queryset(users), - on_results=lambda data: UserSerializer(data, many=True).data, - ) - except Exception as e: - capture_exception(e) - return Response( - {"message": "Something went wrong please try again later"}, - status=status.HTTP_400_BAD_REQUEST, - ) - - class UserEndpoint(BaseViewSet): serializer_class = UserSerializer model = User From 894e26116b5a3075c59e012459617d443fa20cfd Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Tue, 17 Jan 2023 01:50:27 +0530 Subject: [PATCH 03/10] refactor: performance booster optimization (#176) * refactor: setup multiple select related * chore: upgrade sentry sdk to latest version * refactor: update module and cycle views to increase performance * refactor: remove pagination and make the response simillar to paginated API * fix: update staging to DEBUG True for all logging * refactor: update the query count print statement * refactor: my issues endpoint to remove n+1 * refactor: optimize queries for workspace and project * fix: project member endpoint * fix: revert back workspace members * refactor: update base file to remove workspace and project query and update permission layer accordingly * refactor: update read_only fields in read serializers * fix: read only serializers * chore: update drf package * revert: drf version upgrade * revert: read only fields update * revert: update serializer to old state * chore: update drf to latest version * refactor: update dispatch to display method as well * refactor: optimize cycle and module issue queries * refactor: optimize module endpoint and issue list endpoint * refactor: update prefetch related in modules and cycles * refactor: create permission mapping in permission file --- apiserver/plane/api/permissions/project.py | 16 ++++-- apiserver/plane/api/permissions/workspace.py | 20 +++++-- apiserver/plane/api/serializers/__init__.py | 1 - apiserver/plane/api/serializers/issue.py | 2 +- apiserver/plane/api/views/base.py | 55 ++++--------------- apiserver/plane/api/views/cycle.py | 5 +- apiserver/plane/api/views/issue.py | 58 +++++++++++++++++--- apiserver/plane/api/views/module.py | 20 +++++-- apiserver/plane/api/views/project.py | 15 ++++- apiserver/plane/api/views/workspace.py | 8 +-- apiserver/plane/settings/staging.py | 2 +- apiserver/requirements/base.txt | 4 +- 12 files changed, 126 insertions(+), 80 deletions(-) diff --git a/apiserver/plane/api/permissions/project.py b/apiserver/plane/api/permissions/project.py index 44659a671..f87aaf08e 100644 --- a/apiserver/plane/api/permissions/project.py +++ b/apiserver/plane/api/permissions/project.py @@ -4,6 +4,12 @@ from rest_framework.permissions import BasePermission, SAFE_METHODS # 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): @@ -22,14 +28,14 @@ class ProjectBasePermission(BasePermission): 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() @@ -50,14 +56,14 @@ class ProjectMemberPermission(BasePermission): 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() @@ -80,6 +86,6 @@ class ProjectEntityPermission(BasePermission): 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() diff --git a/apiserver/plane/api/permissions/workspace.py b/apiserver/plane/api/permissions/workspace.py index 510d87ce2..2a2e1d339 100644 --- a/apiserver/plane/api/permissions/workspace.py +++ b/apiserver/plane/api/permissions/workspace.py @@ -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 @@ -22,13 +30,15 @@ class WorkSpaceBasePermission(BasePermission): # 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() @@ -39,5 +49,7 @@ class WorkSpaceAdminPermission(BasePermission): 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() diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index e2d474901..ba494ec9e 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -29,7 +29,6 @@ from .issue import ( IssueCommentSerializer, TimeLineIssueSerializer, IssuePropertySerializer, - IssueLabelSerializer, BlockerIssueSerializer, BlockedIssueSerializer, IssueAssigneeSerializer, diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index 569eef88c..227d3b96d 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -443,4 +443,4 @@ class IssueSerializer(BaseSerializer): "updated_by", "created_at", "updated_at", - ] + ] \ No newline at end of file diff --git a/apiserver/plane/api/views/base.py b/apiserver/plane/api/views/base.py index 5d8e75fb3..a4b9ac584 100644 --- a/apiserver/plane/api/views/base.py +++ b/apiserver/plane/api/views/base.py @@ -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 @@ -39,32 +40,23 @@ class BaseViewSet(ModelViewSet, BasePaginator): 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) @@ -74,16 +66,6 @@ class BaseViewSet(ModelViewSet, BasePaginator): 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): @@ -110,33 +92,16 @@ class BaseAPIView(APIView, BasePaginator): 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 diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index 62c0376b3..28125512f 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -32,6 +32,7 @@ class CycleViewSet(BaseViewSet): .filter(project__project_projectmember__member=self.request.user) .select_related("project") .select_related("workspace") + .select_related("owned_by") .distinct() ) @@ -62,8 +63,8 @@ class CycleIssueViewSet(BaseViewSet): .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() ) diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 73ee40a83..dd012c129 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -125,7 +125,9 @@ class IssueViewSet(BaseViewSet): .prefetch_related( Prefetch( "issue_module", - queryset=ModuleIssue.objects.select_related("module", "issue"), + queryset=ModuleIssue.objects.select_related( + "module", "issue" + ).prefetch_related("module__members"), ), ) ) @@ -161,10 +163,18 @@ class IssueViewSet(BaseViewSet): 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: @@ -206,8 +216,42 @@ class IssueViewSet(BaseViewSet): 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) diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index a1aff67a0..4c121f23f 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -15,7 +15,13 @@ from plane.api.serializers import ( 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): @@ -45,13 +51,15 @@ class ModuleViewSet(BaseViewSet): .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"), ) ) ) @@ -117,7 +125,9 @@ class ModuleIssueViewSet(BaseViewSet): .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() ) @@ -164,4 +174,4 @@ class ModuleIssueViewSet(BaseViewSet): return Response( {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, - ) \ No newline at end of file + ) diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index a3113a10a..2ec6faf1e 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -67,7 +67,9 @@ class ProjectViewSet(BaseViewSet): .get_queryset() .filter(workspace__slug=self.kwargs.get("slug")) .filter(Q(project_projectmember__member=self.request.user) | Q(network=2)) - .select_related("workspace", "workspace__owner") + .select_related( + "workspace", "workspace__owner", "default_assignee", "project_lead" + ) .distinct() ) @@ -294,7 +296,7 @@ class UserProjectInvitationsViewset(BaseViewSet): super() .get_queryset() .filter(email=self.request.user.email) - .select_related("workspace") + .select_related("workspace", "workspace__owner", "project") ) def create(self, request): @@ -349,6 +351,7 @@ class ProjectMemberViewSet(BaseViewSet): .filter(project_id=self.kwargs.get("project_id")) .select_related("project") .select_related("member") + .select_related("workspace", "workspace__owner") ) @@ -481,6 +484,7 @@ class ProjectMemberInvitationsViewset(BaseViewSet): .filter(workspace__slug=self.kwargs.get("slug")) .filter(project_id=self.kwargs.get("project_id")) .select_related("project") + .select_related("workspace", "workspace__owner") ) @@ -496,7 +500,12 @@ class ProjectMemberInviteDetailViewSet(BaseViewSet): ] def get_queryset(self): - return self.filter_queryset(super().get_queryset().select_related("project")) + return self.filter_queryset( + super() + .get_queryset() + .select_related("project") + .select_related("workspace", "workspace__owner") + ) class ProjectIdentifierEndpoint(BaseAPIView): diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/api/views/workspace.py index 53f0159c4..e03a80e82 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/api/views/workspace.py @@ -176,7 +176,7 @@ class InviteWorkspaceEndpoint(BaseAPIView): workspace_members = WorkspaceMember.objects.filter( workspace_id=workspace.id, member__email__in=[email.get("email") for email in emails], - ) + ).select_related("member", "worspace", "workspace__owner") if len(workspace_members): return Response( @@ -339,7 +339,7 @@ class WorkspaceInvitationsViewset(BaseViewSet): super() .get_queryset() .filter(workspace__slug=self.kwargs.get("slug")) - .select_related("workspace") + .select_related("workspace", "workspace__owner") ) @@ -353,7 +353,7 @@ class UserWorkspaceInvitationsEndpoint(BaseViewSet): super() .get_queryset() .filter(email=self.request.user.email) - .select_related("workspace") + .select_related("workspace", "workspace__owner") ) def create(self, request): @@ -524,7 +524,7 @@ class UserLastProjectWithWorkspaceEndpoint(BaseAPIView): project_member = ProjectMember.objects.filter( workspace_id=last_workspace_id, member=request.user - ).select_related("workspace", "project", "member") + ).select_related("workspace", "project", "member", "workspace__owner") project_member_serializer = ProjectMemberSerializer( project_member, many=True diff --git a/apiserver/plane/settings/staging.py b/apiserver/plane/settings/staging.py index 2804d4e29..429530d94 100644 --- a/apiserver/plane/settings/staging.py +++ b/apiserver/plane/settings/staging.py @@ -14,7 +14,7 @@ from sentry_sdk.integrations.redis import RedisIntegration from .common import * # noqa # Database -DEBUG = False +DEBUG = True DATABASES = { "default": { "ENGINE": "django.db.backends.postgresql_psycopg2", diff --git a/apiserver/requirements/base.txt b/apiserver/requirements/base.txt index 407c8b8c8..7dff0a765 100644 --- a/apiserver/requirements/base.txt +++ b/apiserver/requirements/base.txt @@ -6,7 +6,7 @@ django-taggit==2.1.0 psycopg2==2.9.3 django-oauth-toolkit==2.0.0 mistune==2.0.3 -djangorestframework==3.13.1 +djangorestframework==3.14.0 redis==4.2.2 django-nested-admin==3.4.0 django-cors-headers==3.11.0 @@ -16,7 +16,7 @@ faker==13.4.0 django-filter==21.1 jsonmodels==2.5.0 djangorestframework-simplejwt==5.1.0 -sentry-sdk==1.5.12 +sentry-sdk==1.13.0 django-s3-storage==0.13.6 django-crum==0.7.9 django-guardian==2.4.0 From acf7f59eef97d611f9162fcbfc2937c89efc44b4 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Tue, 17 Jan 2023 03:33:36 +0530 Subject: [PATCH 04/10] fix: fix nomenclature update on permission filter attributes (#179) --- apiserver/plane/api/permissions/project.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apiserver/plane/api/permissions/project.py b/apiserver/plane/api/permissions/project.py index f87aaf08e..eea5192d5 100644 --- a/apiserver/plane/api/permissions/project.py +++ b/apiserver/plane/api/permissions/project.py @@ -49,7 +49,7 @@ class ProjectMemberPermission(BasePermission): ## Safe Methods -> Handle the filtering logic in queryset if request.method in SAFE_METHODS: return ProjectMember.objects.filter( - workspace=view.workspace, member=request.user + workspace__slug=view.workspace_slug, member=request.user ).exists() ## Only workspace owners or admins can create the projects if request.method == "POST": @@ -77,7 +77,7 @@ class ProjectEntityPermission(BasePermission): ## Safe Methods -> Handle the filtering logic in queryset if request.method in SAFE_METHODS: return ProjectMember.objects.filter( - workspace=view.workspace, + workspace__slug=view.workspace_slug, member=request.user, project_id=view.project_id, ).exists() From e20d30daf2f5c2cc00f41326e90efdf06ec2c9d9 Mon Sep 17 00:00:00 2001 From: sphynxux Date: Wed, 18 Jan 2023 00:56:56 +0530 Subject: [PATCH 05/10] added missing pages, and title text --- apps/docs/src/components/Resources.jsx | 4 ++-- apps/docs/src/pages/architecture.mdx | 5 +++++ apps/docs/src/pages/cycles.mdx | 5 +++++ apps/docs/src/pages/index.mdx | 3 +-- apps/docs/src/pages/issues.mdx | 5 +++++ apps/docs/src/pages/modules.mdx | 5 +++++ 6 files changed, 23 insertions(+), 4 deletions(-) create mode 100644 apps/docs/src/pages/architecture.mdx create mode 100644 apps/docs/src/pages/cycles.mdx create mode 100644 apps/docs/src/pages/issues.mdx create mode 100644 apps/docs/src/pages/modules.mdx diff --git a/apps/docs/src/components/Resources.jsx b/apps/docs/src/components/Resources.jsx index 813063122..187d6cb1a 100644 --- a/apps/docs/src/components/Resources.jsx +++ b/apps/docs/src/components/Resources.jsx @@ -24,7 +24,7 @@ const resources = [ }, }, { - href: '/self-host', + href: '/self-hosting', name: 'Self-host Plane', description: 'Run Plane on your computer or development machine.', icon: ChatBubbleIcon, @@ -51,7 +51,7 @@ const resources = [ }, }, { - href: '/', + href: 'https://discord.com/invite/A92xrEGCge', name: 'Community', description: 'Hang out with truly exceptional devs & designers on Discord.', icon: UsersIcon, diff --git a/apps/docs/src/pages/architecture.mdx b/apps/docs/src/pages/architecture.mdx new file mode 100644 index 000000000..975a876fd --- /dev/null +++ b/apps/docs/src/pages/architecture.mdx @@ -0,0 +1,5 @@ +export const description = 'Plane Architecture' + +# Plane Architecture + +Coming very soon. diff --git a/apps/docs/src/pages/cycles.mdx b/apps/docs/src/pages/cycles.mdx new file mode 100644 index 000000000..07c850fba --- /dev/null +++ b/apps/docs/src/pages/cycles.mdx @@ -0,0 +1,5 @@ +export const description = 'Cycles' + +# Cycles + +Coming very soon. diff --git a/apps/docs/src/pages/index.mdx b/apps/docs/src/pages/index.mdx index 74bf8c67a..47b4e486f 100644 --- a/apps/docs/src/pages/index.mdx +++ b/apps/docs/src/pages/index.mdx @@ -3,8 +3,7 @@ import { Resources } from '@/components/Resources' import { HeroPattern } from '@/components/HeroPattern' import { Heading } from '@/components/Heading' -export const description = - 'Learn everything there is to know about the Protocol API and integrate Protocol into your product.' +export const description = '.' export const sections = [ { title: 'Resources', id: 'resources' }, diff --git a/apps/docs/src/pages/issues.mdx b/apps/docs/src/pages/issues.mdx new file mode 100644 index 000000000..04ec0ea47 --- /dev/null +++ b/apps/docs/src/pages/issues.mdx @@ -0,0 +1,5 @@ +export const description = 'Issues' + +# Issues + +Coming very soon. diff --git a/apps/docs/src/pages/modules.mdx b/apps/docs/src/pages/modules.mdx new file mode 100644 index 000000000..ff3cff6f2 --- /dev/null +++ b/apps/docs/src/pages/modules.mdx @@ -0,0 +1,5 @@ +export const description = 'Modules' + +# Modules + +Coming very soon. From 6e99c007a5abc961647fb82fa9a8798e1151c8bc Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Thu, 26 Jan 2023 11:40:59 +0530 Subject: [PATCH 06/10] fix: make issue description null (#183) * fix: make issue description null * fix: put none check during stripping html --- apiserver/plane/db/models/issue.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index ed72e9b19..f69f11574 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -32,9 +32,9 @@ class Issue(ProjectBaseModel): related_name="state_issue", ) name = models.CharField(max_length=255, verbose_name="Issue Name") - description = models.JSONField(blank=True) - description_html = models.TextField(blank=True) - description_stripped = models.TextField(blank=True) + description = models.JSONField(blank=True, null=True) + description_html = models.TextField(blank=True, null=True) + description_stripped = models.TextField(blank=True, null=True) priority = models.CharField( max_length=30, choices=PRIORITY_CHOICES, @@ -84,10 +84,12 @@ class Issue(ProjectBaseModel): ) except ImportError: pass - + # Strip the html tags using html parser self.description_stripped = ( - strip_tags(self.description_html) if self.description_html != "" else "" + None + if (self.description_html == "" or self.description_html is None) + else strip_tags(self.description_html) ) super(Issue, self).save(*args, **kwargs) @@ -211,10 +213,11 @@ class IssueComment(ProjectBaseModel): ) def save(self, *args, **kwargs): - self.comment_stripped = strip_tags(self.comment_html) if self.comment_html != "" else "" + self.comment_stripped = ( + strip_tags(self.comment_html) if self.comment_html != "" else "" + ) return super(IssueComment, self).save(*args, **kwargs) - class Meta: verbose_name = "Issue Comment" verbose_name_plural = "Issue Comments" From 3036014ea299737c696785957b8b4dbca9658be6 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Thu, 26 Jan 2023 11:41:11 +0530 Subject: [PATCH 07/10] refactor: add annotations in queryset to return sub_issues_count and total members in workspace (#185) * refactor: add anotations in queryset to return sub_issues_count * refactor: add sub issue count in modules cycles and my issues endpoint --- apiserver/plane/api/serializers/cycle.py | 4 ++++ apiserver/plane/api/serializers/issue.py | 3 ++- apiserver/plane/api/serializers/module.py | 3 ++- apiserver/plane/api/views/cycle.py | 9 +++++++++ apiserver/plane/api/views/issue.py | 16 +++++++++++++++- apiserver/plane/api/views/module.py | 8 +++++++- apiserver/plane/api/views/workspace.py | 12 ++++++++++-- 7 files changed, 49 insertions(+), 6 deletions(-) diff --git a/apiserver/plane/api/serializers/cycle.py b/apiserver/plane/api/serializers/cycle.py index 4e125bfae..09f35b669 100644 --- a/apiserver/plane/api/serializers/cycle.py +++ b/apiserver/plane/api/serializers/cycle.py @@ -1,3 +1,6 @@ +# Third party imports +from rest_framework import serializers + # Module imports from .base import BaseSerializer from .user import UserLiteSerializer @@ -22,6 +25,7 @@ class CycleSerializer(BaseSerializer): class CycleIssueSerializer(BaseSerializer): issue_detail = IssueStateSerializer(read_only=True, source="issue") + sub_issues_count = serializers.IntegerField(read_only=True) class Meta: model = CycleIssue diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index 227d3b96d..a148cbfb5 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -432,6 +432,7 @@ class IssueSerializer(BaseSerializer): blocker_issues = BlockerIssueSerializer(read_only=True, many=True) issue_cycle = IssueCycleDetailSerializer(read_only=True) issue_module = IssueModuleDetailSerializer(read_only=True) + sub_issues_count = serializers.IntegerField(read_only=True) class Meta: model = Issue @@ -443,4 +444,4 @@ class IssueSerializer(BaseSerializer): "updated_by", "created_at", "updated_at", - ] \ No newline at end of file + ] diff --git a/apiserver/plane/api/serializers/module.py b/apiserver/plane/api/serializers/module.py index 1d3748f8d..9f165dd28 100644 --- a/apiserver/plane/api/serializers/module.py +++ b/apiserver/plane/api/serializers/module.py @@ -150,6 +150,7 @@ class ModuleIssueSerializer(BaseSerializer): module_detail = ModuleFlatSerializer(read_only=True, source="module") issue_detail = IssueStateSerializer(read_only=True, source="issue") + sub_issues_count = serializers.IntegerField(read_only=True) class Meta: model = ModuleIssue @@ -200,4 +201,4 @@ class ModuleSerializer(BaseSerializer): "updated_by", "created_at", "updated_at", - ] \ No newline at end of file + ] diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index 28125512f..d1b291d9a 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -1,3 +1,6 @@ +# Django imports +from django.db.models import OuterRef, Func, F + # Third party imports from rest_framework.response import Response from rest_framework import status @@ -56,6 +59,12 @@ class CycleIssueViewSet(BaseViewSet): return self.filter_queryset( super() .get_queryset() + .annotate( + sub_issues_count=Issue.objects.filter(parent=OuterRef("issue_id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) .filter(workspace__slug=self.kwargs.get("slug")) .filter(project_id=self.kwargs.get("project_id")) .filter(project__project_projectmember__member=self.request.user) diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index dd012c129..4212934bf 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -3,7 +3,7 @@ import json from itertools import groupby, chain # Django imports -from django.db.models import Prefetch +from django.db.models import Prefetch, OuterRef, Func, F from django.core.serializers.json import DjangoJSONEncoder # Third Party imports @@ -93,9 +93,16 @@ class IssueViewSet(BaseViewSet): return super().perform_update(serializer) def get_queryset(self): + return ( super() .get_queryset() + .annotate( + sub_issues_count=Issue.objects.filter(parent=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) .filter(project_id=self.kwargs.get("project_id")) .filter(workspace__slug=self.kwargs.get("slug")) .select_related("project") @@ -178,6 +185,7 @@ class IssueViewSet(BaseViewSet): ) except Exception as e: + print(e) capture_exception(e) return Response( {"error": "Something went wrong please try again later"}, @@ -218,6 +226,12 @@ class UserWorkSpaceIssues(BaseAPIView): try: issues = ( Issue.objects.filter(assignees__in=[request.user], workspace__slug=slug) + .annotate( + sub_issues_count=Issue.objects.filter(parent=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) .select_related("project") .select_related("workspace") .select_related("state") diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index 4c121f23f..9955ded76 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -1,6 +1,6 @@ # Django Imports from django.db import IntegrityError -from django.db.models import Prefetch +from django.db.models import Prefetch, F, OuterRef, Func # Third party imports from rest_framework.response import Response @@ -118,6 +118,12 @@ class ModuleIssueViewSet(BaseViewSet): return self.filter_queryset( super() .get_queryset() + .annotate( + sub_issues_count=Issue.objects.filter(parent=OuterRef("issue")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) .filter(workspace__slug=self.kwargs.get("slug")) .filter(project_id=self.kwargs.get("project_id")) .filter(module_id=self.kwargs.get("module_id")) diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/api/views/workspace.py index e03a80e82..e8695c818 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/api/views/workspace.py @@ -10,7 +10,7 @@ from django.utils import timezone from django.core.exceptions import ValidationError from django.core.validators import validate_email from django.contrib.sites.shortcuts import get_current_site -from django.db.models import CharField, Count +from django.db.models import CharField, Count, OuterRef, Func, F from django.db.models.functions import Cast # Third party modules @@ -111,6 +111,14 @@ class UserWorkSpacesEndpoint(BaseAPIView): def get(self, request): try: + + member_count = ( + WorkspaceMember.objects.filter(workspace=OuterRef("id")) + .order_by() + .annotate(count=Func(F("id"), function="Count")) + .values("count") + ) + workspace = ( Workspace.objects.prefetch_related( Prefetch("workspace_member", queryset=WorkspaceMember.objects.all()) @@ -119,7 +127,7 @@ class UserWorkSpacesEndpoint(BaseAPIView): workspace_member__member=request.user, ) .select_related("owner") - ).annotate(total_members=Count("workspace_member")) + ).annotate(total_members=member_count) serializer = WorkSpaceSerializer(self.filter_queryset(workspace), many=True) return Response(serializer.data, status=status.HTTP_200_OK) From 0fd96d592d7b23d1dd32e2ac18a868b43c9fd348 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Thu, 26 Jan 2023 11:41:20 +0530 Subject: [PATCH 08/10] feat: sub issue count endpoint (#186) --- apiserver/plane/api/urls.py | 6 +++ apiserver/plane/api/views/__init__.py | 1 + apiserver/plane/api/views/issue.py | 59 +++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index 050ce9c95..38d2b4013 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -65,6 +65,7 @@ from plane.api.views import ( TimeLineIssueViewSet, IssuePropertyViewSet, LabelViewSet, + SubIssuesEndpoint, ## End Issues # States StateViewSet, @@ -540,6 +541,11 @@ urlpatterns = [ UserWorkSpaceIssues.as_view(), name="workspace-issues", ), + path( + "workspaces//projects//issues//sub-issues/", + SubIssuesEndpoint.as_view(), + name="sub-issues", + ), ## End Issues ## Issue Activity path( diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 1cadd95e6..933315277 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -51,6 +51,7 @@ from .issue import ( LabelViewSet, BulkDeleteIssuesEndpoint, UserWorkSpaceIssues, + SubIssuesEndpoint, ) from .auth_extended import ( diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 4212934bf..f716c2b11 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -525,3 +525,62 @@ class BulkDeleteIssuesEndpoint(BaseAPIView): {"error": "Something went wrong please try again later"}, status=status.HTTP_400_BAD_REQUEST, ) + + +class SubIssuesEndpoint(BaseAPIView): + + permission_classes = [ + ProjectEntityPermission, + ] + + def get(self, request, slug, project_id, issue_id): + try: + + sub_issues = ( + Issue.objects.filter( + parent_id=issue_id, workspace__slug=slug, project_id=project_id + ) + .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(sub_issues, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) From 9134b0c543fc792ba662ff0c5d131bb0f58ce895 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Thu, 26 Jan 2023 11:41:31 +0530 Subject: [PATCH 09/10] fix: typo in invitation query (#189) --- apiserver/plane/api/views/workspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/api/views/workspace.py index e8695c818..4b06275aa 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/api/views/workspace.py @@ -184,7 +184,7 @@ class InviteWorkspaceEndpoint(BaseAPIView): workspace_members = WorkspaceMember.objects.filter( workspace_id=workspace.id, member__email__in=[email.get("email") for email in emails], - ).select_related("member", "worspace", "workspace__owner") + ).select_related("member", "workspace", "workspace__owner") if len(workspace_members): return Response( From 9075f9441c863387644bc918e70433b99df8fda2 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Thu, 26 Jan 2023 23:42:20 +0530 Subject: [PATCH 10/10] Refactoring Phase 1 (#199) * style: added cta at the bottom of sidebar, added missing icons as well, showing dynamic workspace member count on workspace dropdown * refractor: running parallel request, made create/edit label function to async function * fix: sidebar dropdown content going below kanban items outside click detection in need help dropdown * refractor: making parallel api calls fix: create state input comes at bottom, create state input gets on focus automatically, form is getting submitted on enter click * refactoring file structure and signin page * style: changed text and added spinner for signing in loading * refractor: removed unused type * fix: my issue cta in profile page sending to 404 page * fix: added new s3 bucket url in next.config.js file increased image modal height * packaging UI components * eslint config * eslint fixes * refactoring changes * build fixes * minor fixes * adding todo comments for reference * refactor: cleared unused imports and re ordered imports * refactor: removed unused imports * fix: added workspace argument to useissues hook * refactor: removed api-routes file, unnecessary constants * refactor: created helpers folder, removed unnecessary constants * refactor: new context for issue view * refactoring issues page * build fixes * refactoring * refactor: create issue modal * refactor: module ui * fix: sub-issues mutation * fix: create more option in create issue modal * description form debounce issue * refactor: global component for assignees list * fix: link module interface * fix: priority icons and sub-issues count added * fix: cycle mutation in issue details page * fix: remove issue from cycle mutation * fix: create issue modal in home page * fix: removed unnecessary props * fix: updated create issue form status * fix: settings auth breaking * refactor: issue details page Co-authored-by: Dakshesh Jain Co-authored-by: Dakshesh Jain <65905942+dakshesh14@users.noreply.github.com> Co-authored-by: venkatesh-soulpage Co-authored-by: Aaryan Khandelwal Co-authored-by: Anmol Singh Bhatia --- .eslintrc.js | 10 + .eslintrc.json | 3 - apps/app/.eslintrc.js | 1 + apps/app/Dockerfile.web | 2 +- .../email-code-form.tsx} | 29 +- .../email-password-form.tsx} | 29 +- .../components/account/email-signin-form.tsx | 46 + .../account/github-login-button.tsx | 51 + .../google-login.tsx | 11 +- apps/app/components/account/index.ts | 5 + .../{ui => components}/breadcrumbs/index.tsx | 40 +- apps/app/components/command-palette/index.tsx | 67 +- .../components/command-palette/shortcuts.tsx | 18 +- .../common/board-view/board-header.tsx | 109 + .../common/board-view/single-board.tsx | 4 +- .../common/board-view/single-issue.tsx | 119 +- .../common/bulk-delete-issues-modal.tsx | 295 +- .../common/existing-issues-list-modal.tsx | 84 +- .../components/common/image-upload-modal.tsx | 18 +- .../common/list-view/single-issue.tsx | 324 +- .../components/core/not-authorized-view.tsx | 4 +- apps/app/components/core/view.tsx | 227 +- apps/app/components/cycles/form.tsx | 135 + apps/app/components/cycles/index.ts | 3 + apps/app/components/cycles/modal.tsx | 112 + apps/app/components/cycles/select.tsx | 131 + .../components/dnd/StrictModeDroppable.tsx | 3 +- .../emoji-icon-picker/emojis.json | 0 .../emoji-icon-picker/helpers.ts | 0 .../emoji-icon-picker/index.tsx | 15 +- .../emoji-icon-picker/types.d.ts | 0 .../icons/attachment-icon.tsx | 4 +- .../{ui => components}/icons/blocked-icon.tsx | 4 +- .../{ui => components}/icons/blocker-icon.tsx | 4 +- .../{ui => components}/icons/bolt-icon.tsx | 4 +- .../icons/calendar-month-icon.tsx | 4 +- .../{ui => components}/icons/cancel-icon.tsx | 4 +- .../icons/clipboard-icon.tsx | 4 +- .../{ui => components}/icons/comment-icon.tsx | 4 +- .../icons/completed-cycle-icon.tsx | 4 +- .../icons/current-cycle-icon.tsx | 4 +- .../{ui => components}/icons/cycle-icon.tsx | 4 +- .../{ui => components}/icons/discord-icon.tsx | 6 +- .../icons/document-icon.tsx | 4 +- .../{ui => components}/icons/edit-icon.tsx | 4 +- .../icons/ellipsis-horizontal-icon.tsx | 4 +- .../{ui => components}/icons/github-icon.tsx | 10 +- .../icons/heartbeat-icon.tsx | 4 +- apps/app/{ui => components}/icons/index.ts | 0 .../icons/layer-diagonal-icon.tsx | 4 +- .../{ui => components}/icons/lock-icon.tsx | 4 +- .../{ui => components}/icons/menu-icon.tsx | 4 +- .../{ui => components}/icons/plus-icon.tsx | 4 +- .../icons/question-mark-circle-icon.tsx | 4 +- .../{ui => components}/icons/setting-icon.tsx | 4 +- .../icons/signal-cellular-icon.tsx | 4 +- .../app/{ui => components}/icons/tag-icon.tsx | 4 +- .../{ui => components}/icons/tune-icon.tsx | 4 +- apps/app/{ui => components}/icons/types.d.ts | 0 .../icons/upcoming-cycle-icon.tsx | 4 +- .../icons/user-group-icon.tsx | 4 +- .../icons/user-icon-circle.tsx | 4 +- .../{ui => components}/icons/user-icon.tsx | 4 +- .../components/issues/description-form.tsx | 74 + apps/app/components/issues/form.tsx | 382 + apps/app/components/issues/index.ts | 5 + apps/app/components/issues/list-item.tsx | 144 + apps/app/components/issues/modal.tsx | 265 + .../app/components/issues/select/assignee.tsx | 166 + apps/app/components/issues/select/index.ts | 6 + apps/app/components/issues/select/label.tsx | 206 + .../components/issues/select/parent-issue.tsx | 28 + .../app/components/issues/select/priority.tsx | 54 + apps/app/components/issues/select/project.tsx | 104 + apps/app/components/issues/select/state.tsx | 138 + apps/app/components/issues/sub-issue-list.tsx | 168 + .../onboarding/break-into-modules.tsx | 6 +- .../components/onboarding/command-menu.tsx | 4 +- .../components/onboarding/invite-members.tsx | 9 +- .../onboarding/move-with-cycles.tsx | 6 +- .../onboarding/plan-with-issues.tsx | 6 +- .../components/onboarding/user-details.tsx | 6 +- apps/app/components/onboarding/welcome.tsx | 4 +- apps/app/components/onboarding/workspace.tsx | 20 +- .../{member-invitations.tsx => card.tsx} | 45 +- .../project/confirm-project-deletion.tsx | 18 +- .../project/confirm-project-member-remove.tsx | 2 +- .../project/create-project-modal.tsx | 26 +- .../project/cycles/board-view/index.tsx | 60 +- .../cycles/board-view/single-board.tsx | 106 +- .../project/cycles/confirm-cycle-deletion.tsx | 17 +- .../cycles/create-update-cycle-modal.tsx | 18 +- .../cycles/cycle-detail-sidebar/index.tsx | 40 +- .../project/cycles/list-view/index.tsx | 47 +- .../project/cycles/stats-view/index.tsx | 2 +- .../project/cycles/stats-view/single-stat.tsx | 70 +- apps/app/components/project/index.ts | 3 + .../project/issues/BoardView/index.tsx | 58 +- .../project/issues/BoardView/single-board.tsx | 132 +- .../BoardView/state/confirm-state-delete.tsx | 21 +- .../state/create-update-state-inline.tsx | 81 +- .../state/create-update-state-modal.tsx | 10 +- .../project/issues/confirm-issue-deletion.tsx | 22 +- .../create-update-issue-modal/index.tsx | 602 -- .../select-assignee.tsx | 62 - .../select-cycle.tsx | 71 - .../select-labels.tsx | 137 - .../select-parent-issue.tsx | 37 - .../select-priority.tsx | 46 - .../select-project.tsx | 116 - .../select-state.tsx | 73 - .../issues/issue-detail/activity/index.tsx | 35 +- .../issues/issue-detail/add-as-sub-issue.tsx | 29 +- .../comment/issue-comment-card.tsx | 17 +- .../comment/issue-comment-section.tsx | 18 +- .../issue-detail-sidebar/index.tsx | 59 +- .../issue-detail-sidebar/select-assignee.tsx | 69 +- .../issue-detail-sidebar/select-blocked.tsx | 215 +- .../issue-detail-sidebar/select-blocker.tsx | 261 +- .../issue-detail-sidebar/select-cycle.tsx | 109 +- .../issue-detail-sidebar/select-module.tsx | 19 +- .../issue-detail-sidebar/select-parent.tsx | 7 +- .../issue-detail-sidebar/select-priority.tsx | 96 +- .../issue-detail-sidebar/select-state.tsx | 22 +- .../project/issues/issues-list-modal.tsx | 38 +- .../project/issues/list-view/index.tsx | 444 +- ...Dropdown.tsx => change-state-dropdown.tsx} | 30 +- .../components/project/join-project-modal.tsx | 6 +- apps/app/components/project/join-project.tsx | 25 + .../project/modules/board-view/index.tsx | 38 +- .../modules/board-view/single-board.tsx | 117 +- .../modules/confirm-module-deletion.tsx | 4 +- .../create-update-module-modal/index.tsx | 14 +- .../select-lead.tsx | 32 +- .../select-members.tsx | 32 +- .../select-status.tsx | 20 +- .../project/modules/list-view/index.tsx | 49 +- .../modules/module-detail-sidebar/index.tsx | 46 +- .../module-detail-sidebar/select-lead.tsx | 17 +- .../module-detail-sidebar/select-members.tsx | 18 +- .../module-detail-sidebar/select-status.tsx | 99 +- .../project/modules/module-link-modal.tsx | 17 +- .../project/modules/single-module-card.tsx | 42 +- .../project/send-project-invitation-modal.tsx | 24 +- .../project/settings/single-label.tsx | 47 +- apps/app/components/project/sidebar-list.tsx | 208 + .../app/components/rich-text-editor/index.tsx | 71 +- .../rich-text-editor/mention-autocomplete.tsx | 35 +- .../components/rich-text-editor/sample.tsx | 6 +- .../toolbar/heading-controls.tsx | 2 +- .../rich-text-editor/toolbar/index.tsx | 52 +- .../search-listbox/index.tsx | 19 +- .../search-listbox/types.d.ts | 0 apps/app/components/sidebar/projects-list.tsx | 52 +- .../components/sidebar/workspace-options.tsx | 212 - apps/app/components/toast-alert/index.tsx | 12 +- apps/app/components/ui/avatar/avatar.tsx | 90 + apps/app/components/ui/avatar/index.ts | 1 + apps/app/components/ui/button/index.tsx | 62 + .../components/ui/custom-listbox/index.tsx | 122 + .../ui/custom-listbox/types.d.ts | 1 - apps/app/components/ui/custom-menu/index.tsx | 123 + .../ui/custom-menu/types.d.ts | 0 .../app/components/ui/custom-select/index.tsx | 99 + apps/app/components/ui/empty-space/index.tsx | 79 + .../app/components/ui/header-button/index.tsx | 33 + apps/app/components/ui/index.ts | 15 + apps/app/components/ui/input/index.tsx | 50 + apps/app/{ => components}/ui/input/types.d.ts | 6 +- apps/app/components/ui/loader/index.tsx | 29 + apps/app/{ => components}/ui/modal/index.tsx | 6 +- .../{ => components}/ui/multi-input/index.tsx | 13 +- .../components/ui/outline-button/index.tsx | 62 + apps/app/{ => components}/ui/select/index.tsx | 16 +- .../app/{ => components}/ui/select/types.d.ts | 4 +- .../app/{ => components}/ui/spinner/index.tsx | 12 +- .../{ => components}/ui/text-area/index.tsx | 47 +- .../{ => components}/ui/text-area/types.d.ts | 0 .../app/{ => components}/ui/tooltip/index.tsx | 6 +- .../workspace/confirm-workspace-deletion.tsx | 10 +- .../confirm-workspace-member-remove.tsx | 2 +- .../app/components/workspace/help-section.tsx | 140 + .../components/workspace/home-cards-list.tsx | 37 + .../components/workspace/home-greetings.tsx | 31 + apps/app/components/workspace/index.ts | 5 + .../send-workspace-invitation-modal.tsx | 12 +- .../components/workspace/sidebar-dropdown.tsx | 214 + .../app/components/workspace/sidebar-menu.tsx | 71 + apps/app/constants/api-routes.ts | 149 - apps/app/constants/common.ts | 238 - apps/app/constants/fetch-keys.ts | 4 + apps/app/constants/index.ts | 1 - apps/app/constants/{seo => }/seo-variables.ts | 0 apps/app/constants/theme.context.constants.ts | 7 - apps/app/constants/toast.context.constants.ts | 2 - apps/app/contexts/issue-view.context.tsx | 283 + apps/app/contexts/theme.context.tsx | 249 +- apps/app/contexts/toast.context.tsx | 14 +- apps/app/contexts/user.context.tsx | 2 +- apps/app/contexts/workspace.context.tsx | 51 + apps/app/helpers/array.helper.ts | 27 + apps/app/helpers/date-time.helper.ts | 78 + apps/app/helpers/functions.helper.ts | 37 + apps/app/helpers/string.helper.ts | 87 + apps/app/hooks/use-debounce.tsx | 19 + .../use-issue-properties.tsx} | 17 +- .../use-issue-view.tsx} | 39 +- apps/app/hooks/use-issues.tsx | 22 + .../use-local-storage.tsx} | 1 + .../use-my-issues-filter.tsx} | 22 +- .../use-outside-click-detector.tsx} | 0 apps/app/hooks/use-project-members.tsx | 33 + apps/app/hooks/use-projects.tsx | 31 + .../useTheme.tsx => hooks/use-theme.tsx} | 2 - .../useToast.tsx => hooks/use-toast.tsx} | 2 - .../hooks/useUser.tsx => hooks/use-user.tsx} | 2 +- apps/app/hooks/use-workspace-details.tsx | 37 + apps/app/hooks/use-workspaces.tsx | 31 + apps/app/layouts/app-layout.tsx | 118 - apps/app/layouts/app-layout/app-header.tsx | 37 + apps/app/layouts/app-layout/app-sidebar.tsx | 44 + apps/app/layouts/app-layout/index.tsx | 120 + apps/app/layouts/container.tsx | 3 +- apps/app/layouts/default-layout.tsx | 19 +- apps/app/layouts/navbar/header.tsx | 2 +- apps/app/layouts/navbar/main-sidebar.tsx | 21 +- apps/app/layouts/settings-layout.tsx | 6 +- apps/app/lib/auth.ts | 42 +- apps/app/lib/cookie.ts | 1 + apps/app/lib/hoc/withAuthWrapper.tsx | 33 - apps/app/lib/hooks/useAutosizeTextArea.tsx | 21 - apps/app/lib/redirect.ts | 3 +- apps/app/lib/services/project.service.ts | 248 - apps/app/lib/services/state.service.ts | 98 - apps/app/manifest.json | 32 + apps/app/next.config.js | 29 +- apps/app/package.json | 25 +- apps/app/pages/404.tsx | 62 +- apps/app/pages/[workspaceSlug]/index.tsx | 188 +- .../pages/[workspaceSlug]/me/my-issues.tsx | 85 +- apps/app/pages/[workspaceSlug]/me/profile.tsx | 52 +- .../[workspaceSlug]/me/workspace-invites.tsx | 24 +- apps/app/pages/[workspaceSlug]/members.tsx | 312 - .../projects/[projectId]/cycles/[cycleId].tsx | 175 +- .../projects/[projectId]/cycles/index.tsx | 27 +- .../projects/[projectId]/issues/[issueId].tsx | 346 +- .../projects/[projectId]/issues/index.tsx | 219 +- .../projects/[projectId]/members.tsx | 346 - .../[projectId]/modules/[moduleId].tsx | 146 +- .../projects/[projectId]/modules/index.tsx | 25 +- .../projects/[projectId]/settings/control.tsx | 11 +- .../projects/[projectId]/settings/index.tsx | 53 +- .../projects/[projectId]/settings/labels.tsx | 67 +- .../projects/[projectId]/settings/members.tsx | 31 +- .../projects/[projectId]/settings/states.tsx | 83 +- .../pages/[workspaceSlug]/projects/index.tsx | 64 +- .../[workspaceSlug]/settings/billing.tsx | 10 +- .../[workspaceSlug]/settings/features.tsx | 16 +- .../pages/[workspaceSlug]/settings/index.tsx | 42 +- .../[workspaceSlug]/settings/members.tsx | 31 +- apps/app/pages/_app.tsx | 10 +- apps/app/pages/_document.tsx | 22 + apps/app/pages/api/hello.ts | 13 - .../index.tsx} | 19 +- apps/app/pages/error.tsx | 4 +- apps/app/pages/index.tsx | 4 +- .../index.tsx} | 39 +- .../index.tsx} | 10 +- .../{onboarding.tsx => onboarding/index.tsx} | 6 +- apps/app/pages/signin.tsx | 184 +- .../[invitationId].tsx | 34 +- apps/app/postcss.config.js | 5 +- apps/app/public/sw.js | 101 + apps/app/public/sw.js.map | 1 + apps/app/public/workbox-7805bd61.js | 2458 ++++++ apps/app/public/workbox-7805bd61.js.map | 1 + apps/app/{lib => }/services/api.service.ts | 0 .../services/authentication.service.ts | 20 +- apps/app/{lib => }/services/cycles.service.ts | 42 +- apps/app/{lib => }/services/file.service.ts | 10 +- apps/app/{lib => }/services/issues.service.ts | 190 +- .../app/{lib => }/services/modules.service.ts | 78 +- apps/app/services/project.service.ts | 206 + apps/app/services/state.service.ts | 88 + apps/app/{lib => }/services/user.service.ts | 27 +- .../{lib => }/services/workspace.service.ts | 143 +- apps/app/styles/editor.css | 7 + apps/app/tailwind.config.js | 4 + apps/app/tsconfig.json | 55 +- apps/app/types/cycles.d.ts | 3 +- apps/app/types/issues.d.ts | 2 + apps/app/types/modules.d.ts | 2 + apps/app/types/users.d.ts | 1 + apps/app/ui/button/index.tsx | 71 - apps/app/ui/custom-listbox/index.tsx | 126 - apps/app/ui/custom-menu/index.tsx | 130 - apps/app/ui/custom-select/index.tsx | 108 - apps/app/ui/empty-space/index.tsx | 97 - apps/app/ui/header-button/index.tsx | 40 - apps/app/ui/index.ts | 16 - apps/app/ui/input/index.tsx | 58 - apps/app/ui/loader/index.tsx | 33 - apps/app/ui/outline-button/index.tsx | 67 - apps/app/yarn.lock | 2428 ------ apps/docs/yarn.lock | 3333 --------- package.json | 9 +- packages/config/.eslintrc.js | 45 + packages/config/index.js | 8 + packages/config/package.json | 19 + packages/config/postcss.config.js | 6 + packages/config/tailwind.config.js | 10 + packages/tsconfig/base.json | 20 + packages/tsconfig/nextjs.json | 22 + packages/tsconfig/package.json | 10 + packages/tsconfig/react-library.json | 11 + packages/ui/index.tsx | 17 + packages/ui/package.json | 21 + packages/ui/tsconfig.json | 9 + pnpm-lock.yaml | 3632 +++++++-- pnpm-workspace.yaml | 3 +- turbo.json | 5 + yarn.lock | 6652 ----------------- 322 files changed, 14149 insertions(+), 21378 deletions(-) create mode 100644 .eslintrc.js delete mode 100644 .eslintrc.json create mode 100644 apps/app/.eslintrc.js rename apps/app/components/{forms/EmailCodeForm.tsx => account/email-code-form.tsx} (84%) rename apps/app/components/{forms/EmailPasswordForm.tsx => account/email-password-form.tsx} (79%) create mode 100644 apps/app/components/account/email-signin-form.tsx create mode 100644 apps/app/components/account/github-login-button.tsx rename apps/app/components/{socialbuttons => account}/google-login.tsx (89%) create mode 100644 apps/app/components/account/index.ts rename apps/app/{ui => components}/breadcrumbs/index.tsx (69%) create mode 100644 apps/app/components/common/board-view/board-header.tsx create mode 100644 apps/app/components/cycles/form.tsx create mode 100644 apps/app/components/cycles/index.ts create mode 100644 apps/app/components/cycles/modal.tsx create mode 100644 apps/app/components/cycles/select.tsx rename apps/app/{ui => components}/emoji-icon-picker/emojis.json (100%) rename apps/app/{ui => components}/emoji-icon-picker/helpers.ts (100%) rename apps/app/{ui => components}/emoji-icon-picker/index.tsx (95%) rename apps/app/{ui => components}/emoji-icon-picker/types.d.ts (100%) rename apps/app/{ui => components}/icons/attachment-icon.tsx (97%) rename apps/app/{ui => components}/icons/blocked-icon.tsx (97%) rename apps/app/{ui => components}/icons/blocker-icon.tsx (97%) rename apps/app/{ui => components}/icons/bolt-icon.tsx (95%) rename apps/app/{ui => components}/icons/calendar-month-icon.tsx (98%) rename apps/app/{ui => components}/icons/cancel-icon.tsx (98%) rename apps/app/{ui => components}/icons/clipboard-icon.tsx (96%) rename apps/app/{ui => components}/icons/comment-icon.tsx (95%) rename apps/app/{ui => components}/icons/completed-cycle-icon.tsx (95%) rename apps/app/{ui => components}/icons/current-cycle-icon.tsx (97%) rename apps/app/{ui => components}/icons/cycle-icon.tsx (97%) rename apps/app/{ui => components}/icons/discord-icon.tsx (97%) rename apps/app/{ui => components}/icons/document-icon.tsx (97%) rename apps/app/{ui => components}/icons/edit-icon.tsx (98%) rename apps/app/{ui => components}/icons/ellipsis-horizontal-icon.tsx (97%) rename apps/app/{ui => components}/icons/github-icon.tsx (91%) rename apps/app/{ui => components}/icons/heartbeat-icon.tsx (91%) rename apps/app/{ui => components}/icons/index.ts (100%) rename apps/app/{ui => components}/icons/layer-diagonal-icon.tsx (99%) rename apps/app/{ui => components}/icons/lock-icon.tsx (97%) rename apps/app/{ui => components}/icons/menu-icon.tsx (97%) rename apps/app/{ui => components}/icons/plus-icon.tsx (96%) rename apps/app/{ui => components}/icons/question-mark-circle-icon.tsx (99%) rename apps/app/{ui => components}/icons/setting-icon.tsx (98%) rename apps/app/{ui => components}/icons/signal-cellular-icon.tsx (96%) rename apps/app/{ui => components}/icons/tag-icon.tsx (98%) rename apps/app/{ui => components}/icons/tune-icon.tsx (98%) rename apps/app/{ui => components}/icons/types.d.ts (100%) rename apps/app/{ui => components}/icons/upcoming-cycle-icon.tsx (96%) rename apps/app/{ui => components}/icons/user-group-icon.tsx (98%) rename apps/app/{ui => components}/icons/user-icon-circle.tsx (98%) rename apps/app/{ui => components}/icons/user-icon.tsx (97%) create mode 100644 apps/app/components/issues/description-form.tsx create mode 100644 apps/app/components/issues/form.tsx create mode 100644 apps/app/components/issues/index.ts create mode 100644 apps/app/components/issues/list-item.tsx create mode 100644 apps/app/components/issues/modal.tsx create mode 100644 apps/app/components/issues/select/assignee.tsx create mode 100644 apps/app/components/issues/select/index.ts create mode 100644 apps/app/components/issues/select/label.tsx create mode 100644 apps/app/components/issues/select/parent-issue.tsx create mode 100644 apps/app/components/issues/select/priority.tsx create mode 100644 apps/app/components/issues/select/project.tsx create mode 100644 apps/app/components/issues/select/state.tsx create mode 100644 apps/app/components/issues/sub-issue-list.tsx rename apps/app/components/project/{member-invitations.tsx => card.tsx} (78%) delete mode 100644 apps/app/components/project/issues/create-update-issue-modal/index.tsx delete mode 100644 apps/app/components/project/issues/create-update-issue-modal/select-assignee.tsx delete mode 100644 apps/app/components/project/issues/create-update-issue-modal/select-cycle.tsx delete mode 100644 apps/app/components/project/issues/create-update-issue-modal/select-labels.tsx delete mode 100644 apps/app/components/project/issues/create-update-issue-modal/select-parent-issue.tsx delete mode 100644 apps/app/components/project/issues/create-update-issue-modal/select-priority.tsx delete mode 100644 apps/app/components/project/issues/create-update-issue-modal/select-project.tsx delete mode 100644 apps/app/components/project/issues/create-update-issue-modal/select-state.tsx rename apps/app/components/project/issues/my-issues/{ChangeStateDropdown.tsx => change-state-dropdown.tsx} (83%) create mode 100644 apps/app/components/project/join-project.tsx create mode 100644 apps/app/components/project/sidebar-list.tsx rename apps/app/{ui => components}/search-listbox/index.tsx (92%) rename apps/app/{ui => components}/search-listbox/types.d.ts (100%) create mode 100644 apps/app/components/ui/avatar/avatar.tsx create mode 100644 apps/app/components/ui/avatar/index.ts create mode 100644 apps/app/components/ui/button/index.tsx create mode 100644 apps/app/components/ui/custom-listbox/index.tsx rename apps/app/{ => components}/ui/custom-listbox/types.d.ts (84%) create mode 100644 apps/app/components/ui/custom-menu/index.tsx rename apps/app/{ => components}/ui/custom-menu/types.d.ts (100%) create mode 100644 apps/app/components/ui/custom-select/index.tsx create mode 100644 apps/app/components/ui/empty-space/index.tsx create mode 100644 apps/app/components/ui/header-button/index.tsx create mode 100644 apps/app/components/ui/index.ts create mode 100644 apps/app/components/ui/input/index.tsx rename apps/app/{ => components}/ui/input/types.d.ts (72%) create mode 100644 apps/app/components/ui/loader/index.tsx rename apps/app/{ => components}/ui/modal/index.tsx (97%) rename apps/app/{ => components}/ui/multi-input/index.tsx (87%) create mode 100644 apps/app/components/ui/outline-button/index.tsx rename apps/app/{ => components}/ui/select/index.tsx (77%) rename apps/app/{ => components}/ui/select/types.d.ts (76%) rename apps/app/{ => components}/ui/spinner/index.tsx (92%) rename apps/app/{ => components}/ui/text-area/index.tsx (52%) rename apps/app/{ => components}/ui/text-area/types.d.ts (100%) rename apps/app/{ => components}/ui/tooltip/index.tsx (96%) create mode 100644 apps/app/components/workspace/help-section.tsx create mode 100644 apps/app/components/workspace/home-cards-list.tsx create mode 100644 apps/app/components/workspace/home-greetings.tsx create mode 100644 apps/app/components/workspace/index.ts create mode 100644 apps/app/components/workspace/sidebar-dropdown.tsx create mode 100644 apps/app/components/workspace/sidebar-menu.tsx delete mode 100644 apps/app/constants/api-routes.ts delete mode 100644 apps/app/constants/common.ts rename apps/app/constants/{seo => }/seo-variables.ts (100%) delete mode 100644 apps/app/constants/theme.context.constants.ts delete mode 100644 apps/app/constants/toast.context.constants.ts create mode 100644 apps/app/contexts/issue-view.context.tsx create mode 100644 apps/app/contexts/workspace.context.tsx create mode 100644 apps/app/helpers/array.helper.ts create mode 100644 apps/app/helpers/date-time.helper.ts create mode 100644 apps/app/helpers/functions.helper.ts create mode 100644 apps/app/helpers/string.helper.ts create mode 100644 apps/app/hooks/use-debounce.tsx rename apps/app/{lib/hooks/useIssuesProperties.tsx => hooks/use-issue-properties.tsx} (90%) rename apps/app/{lib/hooks/useIssuesFilter.tsx => hooks/use-issue-view.tsx} (87%) create mode 100644 apps/app/hooks/use-issues.tsx rename apps/app/{lib/hooks/useLocalStorage.tsx => hooks/use-local-storage.tsx} (98%) rename apps/app/{lib/hooks/useMyIssueFilter.tsx => hooks/use-my-issues-filter.tsx} (92%) rename apps/app/{lib/hooks/useOutsideClickDetector.tsx => hooks/use-outside-click-detector.tsx} (100%) create mode 100644 apps/app/hooks/use-project-members.tsx create mode 100644 apps/app/hooks/use-projects.tsx rename apps/app/{lib/hooks/useTheme.tsx => hooks/use-theme.tsx} (99%) rename apps/app/{lib/hooks/useToast.tsx => hooks/use-toast.tsx} (99%) rename apps/app/{lib/hooks/useUser.tsx => hooks/use-user.tsx} (97%) create mode 100644 apps/app/hooks/use-workspace-details.tsx create mode 100644 apps/app/hooks/use-workspaces.tsx delete mode 100644 apps/app/layouts/app-layout.tsx create mode 100644 apps/app/layouts/app-layout/app-header.tsx create mode 100644 apps/app/layouts/app-layout/app-sidebar.tsx create mode 100644 apps/app/layouts/app-layout/index.tsx delete mode 100644 apps/app/lib/hoc/withAuthWrapper.tsx delete mode 100644 apps/app/lib/hooks/useAutosizeTextArea.tsx delete mode 100644 apps/app/lib/services/project.service.ts delete mode 100644 apps/app/lib/services/state.service.ts create mode 100644 apps/app/manifest.json delete mode 100644 apps/app/pages/[workspaceSlug]/members.tsx delete mode 100644 apps/app/pages/[workspaceSlug]/projects/[projectId]/members.tsx create mode 100644 apps/app/pages/_document.tsx delete mode 100644 apps/app/pages/api/hello.ts rename apps/app/pages/{create-workspace.tsx => create-workspace/index.tsx} (98%) rename apps/app/pages/{invitations.tsx => invitations/index.tsx} (89%) rename apps/app/pages/{magic-sign-in.tsx => magic-sign-in/index.tsx} (95%) rename apps/app/pages/{onboarding.tsx => onboarding/index.tsx} (97%) create mode 100644 apps/app/public/sw.js create mode 100644 apps/app/public/sw.js.map create mode 100644 apps/app/public/workbox-7805bd61.js create mode 100644 apps/app/public/workbox-7805bd61.js.map rename apps/app/{lib => }/services/api.service.ts (100%) rename apps/app/{lib => }/services/authentication.service.ts (71%) rename apps/app/{lib => }/services/cycles.service.ts (58%) rename apps/app/{lib => }/services/file.service.ts (61%) rename apps/app/{lib => }/services/issues.service.ts (53%) rename apps/app/{lib => }/services/modules.service.ts (57%) create mode 100644 apps/app/services/project.service.ts create mode 100644 apps/app/services/state.service.ts rename apps/app/{lib => }/services/user.service.ts (60%) rename apps/app/{lib => }/services/workspace.service.ts (53%) delete mode 100644 apps/app/ui/button/index.tsx delete mode 100644 apps/app/ui/custom-listbox/index.tsx delete mode 100644 apps/app/ui/custom-menu/index.tsx delete mode 100644 apps/app/ui/custom-select/index.tsx delete mode 100644 apps/app/ui/empty-space/index.tsx delete mode 100644 apps/app/ui/header-button/index.tsx delete mode 100644 apps/app/ui/index.ts delete mode 100644 apps/app/ui/input/index.tsx delete mode 100644 apps/app/ui/loader/index.tsx delete mode 100644 apps/app/ui/outline-button/index.tsx delete mode 100644 apps/app/yarn.lock delete mode 100644 apps/docs/yarn.lock create mode 100644 packages/config/.eslintrc.js create mode 100644 packages/config/index.js create mode 100644 packages/config/package.json create mode 100644 packages/config/postcss.config.js create mode 100644 packages/config/tailwind.config.js create mode 100644 packages/tsconfig/base.json create mode 100644 packages/tsconfig/nextjs.json create mode 100644 packages/tsconfig/package.json create mode 100644 packages/tsconfig/react-library.json create mode 100644 packages/ui/index.tsx create mode 100644 packages/ui/package.json create mode 100644 packages/ui/tsconfig.json delete mode 100644 yarn.lock diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 000000000..be1ad0f9d --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,10 @@ +module.exports = { + root: true, + // This tells ESLint to load the config from the package `config` + // extends: ["custom"], + settings: { + next: { + rootDir: ["apps/*/"], + }, + }, +}; diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index bffb357a7..000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "next/core-web-vitals" -} diff --git a/apps/app/.eslintrc.js b/apps/app/.eslintrc.js new file mode 100644 index 000000000..64b6ff36b --- /dev/null +++ b/apps/app/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require("config/.eslintrc"); diff --git a/apps/app/Dockerfile.web b/apps/app/Dockerfile.web index 8b3bc4656..be8abf5fa 100644 --- a/apps/app/Dockerfile.web +++ b/apps/app/Dockerfile.web @@ -14,7 +14,7 @@ ENV PATH="${PATH}:./pnpm" COPY ./apps ./apps COPY ./package.json ./package.json -COPY ./.eslintrc.json ./.eslintrc.json +COPY ./.eslintrc.js ./.eslintrc.js COPY ./turbo.json ./turbo.json COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml COPY ./pnpm-lock.yaml ./pnpm-lock.yaml diff --git a/apps/app/components/forms/EmailCodeForm.tsx b/apps/app/components/account/email-code-form.tsx similarity index 84% rename from apps/app/components/forms/EmailCodeForm.tsx rename to apps/app/components/account/email-code-form.tsx index 6c3ef8f86..ff5f8632d 100644 --- a/apps/app/components/forms/EmailCodeForm.tsx +++ b/apps/app/components/account/email-code-form.tsx @@ -1,28 +1,28 @@ import React, { useState } from "react"; -// react hook form import { useForm } from "react-hook-form"; // ui -import { Button, Input } from "ui"; -import authenticationService from "lib/services/authentication.service"; -// icons import { CheckCircleIcon } from "@heroicons/react/20/solid"; +import { Button, Input } from "components/ui"; +// services +import authenticationService from "services/authentication.service"; +// icons // types -type SignIn = { +type EmailCodeFormValues = { email: string; key?: string; token?: string; }; -const EmailCodeForm = ({ onSuccess }: any) => { +export const EmailCodeForm = ({ onSuccess }: any) => { const [codeSent, setCodeSent] = useState(false); const { register, handleSubmit, setError, setValue, - formState: { errors, isSubmitting, dirtyFields, isValid, isDirty }, - } = useForm({ + formState: { errors, isSubmitting, isValid, isDirty }, + } = useForm({ defaultValues: { email: "", key: "", @@ -32,9 +32,8 @@ const EmailCodeForm = ({ onSuccess }: any) => { reValidateMode: "onChange", }); - const onSubmit = ({ email }: SignIn) => { + const onSubmit = ({ email }: EmailCodeFormValues) => { console.log(email); - authenticationService .emailCode({ email }) .then((res) => { @@ -46,15 +45,15 @@ const EmailCodeForm = ({ onSuccess }: any) => { }); }; - const handleSignin = (formData: SignIn) => { + const handleSignin = (formData: EmailCodeFormValues) => { authenticationService .magicSignIn(formData) - .then(async (response) => { - await onSuccess(response); + .then((response) => { + onSuccess(response); }) .catch((error) => { console.log(error); - setError("token" as keyof SignIn, { + setError("token" as keyof EmailCodeFormValues, { type: "manual", message: error.error, }); @@ -127,5 +126,3 @@ const EmailCodeForm = ({ onSuccess }: any) => { ); }; - -export default EmailCodeForm; diff --git a/apps/app/components/forms/EmailPasswordForm.tsx b/apps/app/components/account/email-password-form.tsx similarity index 79% rename from apps/app/components/forms/EmailPasswordForm.tsx rename to apps/app/components/account/email-password-form.tsx index 384b77598..029485b8b 100644 --- a/apps/app/components/forms/EmailPasswordForm.tsx +++ b/apps/app/components/account/email-password-form.tsx @@ -1,29 +1,26 @@ import React from "react"; // next import Link from "next/link"; -import { useRouter } from "next/router"; // react hook form import { useForm } from "react-hook-form"; // ui -import { Button, Input } from "ui"; -import authenticationService from "lib/services/authentication.service"; +import { Button, Input } from "components/ui"; +import authenticationService from "services/authentication.service"; // types -type SignIn = { +type EmailPasswordFormValues = { email: string; password?: string; medium?: string; }; -const EmailPasswordForm = ({ onSuccess }: any) => { +export const EmailPasswordForm = ({ onSuccess }: any) => { const { register, handleSubmit, setError, - setValue, - getValues, - formState: { errors, isSubmitting, dirtyFields, isValid, isDirty }, - } = useForm({ + formState: { errors, isSubmitting, isValid, isDirty }, + } = useForm({ defaultValues: { email: "", password: "", @@ -33,11 +30,11 @@ const EmailPasswordForm = ({ onSuccess }: any) => { reValidateMode: "onChange", }); - const onSubmit = (formData: SignIn) => { + const onSubmit = (formData: EmailPasswordFormValues) => { authenticationService .emailLogin(formData) - .then(async (response) => { - await onSuccess(response); + .then((response) => { + onSuccess(response); }) .catch((error) => { console.log(error); @@ -45,7 +42,7 @@ const EmailPasswordForm = ({ onSuccess }: any) => { Object.keys(error.response.data).forEach((key) => { const err = error.response.data[key]; console.log("err", err); - setError(key as keyof SignIn, { + setError(key as keyof EmailPasswordFormValues, { type: "manual", message: Array.isArray(err) ? err.join(", ") : err, }); @@ -85,8 +82,8 @@ const EmailPasswordForm = ({ onSuccess }: any) => { placeholder="Enter your password" /> -
-
+
+
Forgot your password? @@ -105,5 +102,3 @@ const EmailPasswordForm = ({ onSuccess }: any) => { ); }; - -export default EmailPasswordForm; diff --git a/apps/app/components/account/email-signin-form.tsx b/apps/app/components/account/email-signin-form.tsx new file mode 100644 index 000000000..3d13ca5a1 --- /dev/null +++ b/apps/app/components/account/email-signin-form.tsx @@ -0,0 +1,46 @@ +import { useState, FC } from "react"; +import { KeyIcon } from "@heroicons/react/24/outline"; +// components +import { EmailCodeForm, EmailPasswordForm } from "components/account"; + +export interface EmailSignInFormProps { + handleSuccess: () => void; +} + +export const EmailSignInForm: FC = (props) => { + const { handleSuccess } = props; + // states + const [useCode, setUseCode] = useState(true); + + return ( + <> + {useCode ? ( + + ) : ( + + )} +
+
+
+
+
+
+ or +
+
+
+ +
+
+ + ); +}; diff --git a/apps/app/components/account/github-login-button.tsx b/apps/app/components/account/github-login-button.tsx new file mode 100644 index 000000000..e93abde88 --- /dev/null +++ b/apps/app/components/account/github-login-button.tsx @@ -0,0 +1,51 @@ +import { useEffect, useState, FC } from "react"; +import Link from "next/link"; +import Image from "next/image"; +import { useRouter } from "next/router"; +// images +import githubImage from "/public/logos/github.png"; + +const { NEXT_PUBLIC_GITHUB_ID } = process.env; + +export interface GithubLoginButtonProps { + handleSignIn: React.Dispatch; +} + +export const GithubLoginButton: FC = (props) => { + const { handleSignIn } = props; + // router + const { + query: { code }, + } = useRouter(); + // states + const [loginCallBackURL, setLoginCallBackURL] = useState(undefined); + + useEffect(() => { + if (code) { + handleSignIn(code.toString()); + } + }, [code, handleSignIn]); + + useEffect(() => { + const origin = + typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; + setLoginCallBackURL(`${origin}/signin` as any); + }, []); + + return ( + + + + ); +}; diff --git a/apps/app/components/socialbuttons/google-login.tsx b/apps/app/components/account/google-login.tsx similarity index 89% rename from apps/app/components/socialbuttons/google-login.tsx rename to apps/app/components/account/google-login.tsx index 6c39c58af..078eef518 100644 --- a/apps/app/components/socialbuttons/google-login.tsx +++ b/apps/app/components/account/google-login.tsx @@ -4,12 +4,13 @@ import Script from "next/script"; export interface IGoogleLoginButton { text?: string; - onSuccess?: (res: any) => void; - onFailure?: (res: any) => void; + handleSignIn: React.Dispatch; styles?: CSSProperties; } export const GoogleLoginButton: FC = (props) => { + const { handleSignIn } = props; + const googleSignInButton = useRef(null); const [gsiScriptLoaded, setGsiScriptLoaded] = useState(false); @@ -17,7 +18,7 @@ export const GoogleLoginButton: FC = (props) => { if (!googleSignInButton.current || gsiScriptLoaded) return; window?.google?.accounts.id.initialize({ client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENTID || "", - callback: props.onSuccess as any, + callback: handleSignIn, }); window?.google?.accounts.id.renderButton( googleSignInButton.current, @@ -32,7 +33,7 @@ export const GoogleLoginButton: FC = (props) => { ); window?.google?.accounts.id.prompt(); // also display the One Tap dialog setGsiScriptLoaded(true); - }, [props.onSuccess, gsiScriptLoaded]); + }, [handleSignIn, gsiScriptLoaded]); useEffect(() => { if (window?.google?.accounts?.id) { @@ -46,7 +47,7 @@ export const GoogleLoginButton: FC = (props) => { return ( <> ); } diff --git a/apps/app/pages/_document.tsx b/apps/app/pages/_document.tsx new file mode 100644 index 000000000..befe7cd17 --- /dev/null +++ b/apps/app/pages/_document.tsx @@ -0,0 +1,22 @@ +import Document, { Html, Head, Main, NextScript } from "next/document"; + +class MyDocument extends Document { + render() { + return ( + + + + + +