From c33fc0e0ec3d96aa3197f1512a64f3c4006caac8 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 16 Jan 2023 23:23:55 +0530 Subject: [PATCH 01/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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/31] 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 ( + + + + + + + /> ) } From f87a9e9d3a3e87d15108afbc4be8ae8c33975f34 Mon Sep 17 00:00:00 2001 From: pablohashescobar <118773738+pablohashescobar@users.noreply.github.com> Date: Mon, 30 Jan 2023 00:58:38 +0530 Subject: [PATCH 15/31] feat: plane API gateway (#188) * feat: create model for api token and endpoint for creating api tokens * feat: add list and delete endpoints for tokens --- apiserver/plane/api/serializers/__init__.py | 4 +- apiserver/plane/api/serializers/api_token.py | 8 +++ apiserver/plane/api/urls.py | 7 +++ apiserver/plane/api/views/__init__.py | 2 + apiserver/plane/api/views/api_token.py | 62 ++++++++++++++++++++ apiserver/plane/db/models/__init__.py | 2 + apiserver/plane/db/models/api_token.py | 39 ++++++++++++ apiserver/plane/db/models/user.py | 3 +- 8 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 apiserver/plane/api/serializers/api_token.py create mode 100644 apiserver/plane/api/views/api_token.py create mode 100644 apiserver/plane/db/models/api_token.py diff --git a/apiserver/plane/api/serializers/__init__.py b/apiserver/plane/api/serializers/__init__.py index ba494ec9e..8d43d90ff 100644 --- a/apiserver/plane/api/serializers/__init__.py +++ b/apiserver/plane/api/serializers/__init__.py @@ -38,4 +38,6 @@ from .issue import ( IssueStateSerializer, ) -from .module import ModuleWriteSerializer, ModuleSerializer, ModuleIssueSerializer \ No newline at end of file +from .module import ModuleWriteSerializer, ModuleSerializer, ModuleIssueSerializer + +from .api_token import APITokenSerializer \ No newline at end of file diff --git a/apiserver/plane/api/serializers/api_token.py b/apiserver/plane/api/serializers/api_token.py new file mode 100644 index 000000000..247b3f0e7 --- /dev/null +++ b/apiserver/plane/api/serializers/api_token.py @@ -0,0 +1,8 @@ +from .base import BaseSerializer +from plane.db.models import APIToken + + +class APITokenSerializer(BaseSerializer): + class Meta: + model = APIToken + fields = "__all__" diff --git a/apiserver/plane/api/urls.py b/apiserver/plane/api/urls.py index 38d2b4013..98c2e87d2 100644 --- a/apiserver/plane/api/urls.py +++ b/apiserver/plane/api/urls.py @@ -84,6 +84,9 @@ from plane.api.views import ( ModuleViewSet, ModuleIssueViewSet, ## End Modules + # Api Tokens + ApiTokenEndpoint, + ## End Api Tokens ) @@ -679,4 +682,8 @@ urlpatterns = [ name="project-module-issues", ), ## End Modules + # API Tokens + path("api-tokens/", ApiTokenEndpoint.as_view(), name="api-token"), + path("api-tokens//", ApiTokenEndpoint.as_view(), name="api-token"), + ## End API Tokens ] diff --git a/apiserver/plane/api/views/__init__.py b/apiserver/plane/api/views/__init__.py index 933315277..1212e0dca 100644 --- a/apiserver/plane/api/views/__init__.py +++ b/apiserver/plane/api/views/__init__.py @@ -72,3 +72,5 @@ from .authentication import ( ) from .module import ModuleViewSet, ModuleIssueViewSet + +from .api_token import ApiTokenEndpoint \ No newline at end of file diff --git a/apiserver/plane/api/views/api_token.py b/apiserver/plane/api/views/api_token.py new file mode 100644 index 000000000..4ed3d9de0 --- /dev/null +++ b/apiserver/plane/api/views/api_token.py @@ -0,0 +1,62 @@ +# Python import +from uuid import uuid4 + +# Third party +from rest_framework.response import Response +from rest_framework import status +from sentry_sdk import capture_exception + +# Module import +from .base import BaseAPIView +from plane.db.models import APIToken +from plane.api.serializers import APITokenSerializer + + +class ApiTokenEndpoint(BaseAPIView): + def post(self, request): + try: + + label = request.data.get("label", str(uuid4().hex)) + + api_token = APIToken.objects.create( + label=label, + user=request.user, + ) + + serializer = APITokenSerializer(api_token) + return Response(serializer.data, status=status.HTTP_201_CREATED) + + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) + + def get(self, request): + try: + api_tokens = APIToken.objects.filter(user=request.user) + serializer = APITokenSerializer(api_tokens, 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, + ) + + def delete(self, request, pk): + try: + api_token = APIToken.objects.get(pk=pk) + api_token.delete() + return Response(status=status.HTTP_204_NO_CONTENT) + except APIToken.DoesNotExist: + return Response( + {"error": "Token does not exists"}, status=status.HTTP_400_BAD_REQUEST + ) + except Exception as e: + capture_exception(e) + return Response( + {"error": "Something went wrong please try again later"}, + status=status.HTTP_400_BAD_REQUEST, + ) diff --git a/apiserver/plane/db/models/__init__.py b/apiserver/plane/db/models/__init__.py index b6a19b67a..ef7ad5b8d 100644 --- a/apiserver/plane/db/models/__init__.py +++ b/apiserver/plane/db/models/__init__.py @@ -38,3 +38,5 @@ from .shortcut import Shortcut from .view import View from .module import Module, ModuleMember, ModuleIssue, ModuleLink + +from .api_token import APIToken \ No newline at end of file diff --git a/apiserver/plane/db/models/api_token.py b/apiserver/plane/db/models/api_token.py new file mode 100644 index 000000000..32ba013bc --- /dev/null +++ b/apiserver/plane/db/models/api_token.py @@ -0,0 +1,39 @@ +# Python imports +from uuid import uuid4 + +# Django imports +from django.db import models +from django.conf import settings + +from .base import BaseModel + + +def generate_label_token(): + return uuid4().hex + + +def generate_token(): + return uuid4().hex + uuid4().hex + + +class APIToken(BaseModel): + + token = models.CharField(max_length=255, unique=True, default=generate_token) + label = models.CharField(max_length=255, default=generate_label_token) + user = models.ForeignKey( + settings.AUTH_USER_MODEL, + on_delete=models.CASCADE, + related_name="bot_tokens", + ) + user_type = models.PositiveSmallIntegerField( + choices=((0, "Human"), (1, "Bot")), default=0 + ) + + class Meta: + verbose_name = "API Token" + verbose_name_plural = "API Tokems" + db_table = "api_tokens" + ordering = ("-created_at",) + + def __str__(self): + return str(self.user.name) diff --git a/apiserver/plane/db/models/user.py b/apiserver/plane/db/models/user.py index 1621b19ea..896681808 100644 --- a/apiserver/plane/db/models/user.py +++ b/apiserver/plane/db/models/user.py @@ -68,6 +68,7 @@ class User(AbstractBaseUser, PermissionsMixin): last_workspace_id = models.UUIDField(null=True) my_issues_prop = models.JSONField(null=True) role = models.CharField(max_length=300, null=True, blank=True) + is_bot = models.BooleanField(default=False) USERNAME_FIELD = "email" @@ -101,7 +102,7 @@ class User(AbstractBaseUser, PermissionsMixin): @receiver(post_save, sender=User) def send_welcome_email(sender, instance, created, **kwargs): try: - if created: + if created and not instance.is_bot: first_name = instance.first_name.capitalize() to_email = instance.email from_email_string = f"Team Plane " From 46973149bffb5b239cc9ac8b632b648b852a8915 Mon Sep 17 00:00:00 2001 From: vamsi Date: Mon, 30 Jan 2023 01:20:05 +0530 Subject: [PATCH 16/31] dev: migrations for gateway proxy related models --- .../db/migrations/0018_auto_20230130_0119.py | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 apiserver/plane/db/migrations/0018_auto_20230130_0119.py diff --git a/apiserver/plane/db/migrations/0018_auto_20230130_0119.py b/apiserver/plane/db/migrations/0018_auto_20230130_0119.py new file mode 100644 index 000000000..500bc3b28 --- /dev/null +++ b/apiserver/plane/db/migrations/0018_auto_20230130_0119.py @@ -0,0 +1,57 @@ +# Generated by Django 3.2.16 on 2023-01-29 19:49 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import plane.db.models.api_token +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ('db', '0017_alter_workspace_unique_together'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='is_bot', + field=models.BooleanField(default=False), + ), + migrations.AlterField( + model_name='issue', + name='description', + field=models.JSONField(blank=True, null=True), + ), + migrations.AlterField( + model_name='issue', + name='description_html', + field=models.TextField(blank=True, null=True), + ), + migrations.AlterField( + model_name='issue', + name='description_stripped', + field=models.TextField(blank=True, null=True), + ), + migrations.CreateModel( + name='APIToken', + fields=[ + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Created At')), + ('updated_at', models.DateTimeField(auto_now=True, verbose_name='Last Modified At')), + ('id', models.UUIDField(db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True)), + ('token', models.CharField(default=plane.db.models.api_token.generate_token, max_length=255, unique=True)), + ('label', models.CharField(default=plane.db.models.api_token.generate_label_token, max_length=255)), + ('user_type', models.PositiveSmallIntegerField(choices=[(0, 'Human'), (1, 'Bot')], default=0)), + ('created_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='apitoken_created_by', to=settings.AUTH_USER_MODEL, verbose_name='Created By')), + ('updated_by', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='apitoken_updated_by', to=settings.AUTH_USER_MODEL, verbose_name='Last Modified By')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='bot_tokens', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'API Token', + 'verbose_name_plural': 'API Tokems', + 'db_table': 'api_tokens', + 'ordering': ('-created_at',), + }, + ), + ] From 7ca1aef2ad2d7a57df4a606a0ea7ded4eb6044a9 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Mon, 30 Jan 2023 19:35:18 +0530 Subject: [PATCH 17/31] fix: fetch issue view context props error (#205) * fix: build errors in docs * fix: fetching issue view props error --- apps/app/contexts/issue-view.context.tsx | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/apps/app/contexts/issue-view.context.tsx b/apps/app/contexts/issue-view.context.tsx index 8725a1bf6..149c1c31b 100644 --- a/apps/app/contexts/issue-view.context.tsx +++ b/apps/app/contexts/issue-view.context.tsx @@ -1,4 +1,4 @@ -import { createContext, useCallback, useReducer } from "react"; +import { createContext, useCallback, useEffect, useReducer } from "react"; import { useRouter } from "next/router"; @@ -24,6 +24,7 @@ type IssueViewProps = { type ReducerActionType = { type: + | "REHYDRATE_THEME" | "SET_ISSUE_VIEW" | "SET_ORDER_BY_PROPERTY" | "SET_FILTER_ISSUES" @@ -65,6 +66,12 @@ export const reducer: ReducerFunctionType = (state, action) => { const { type, payload } = action; switch (type) { + case "REHYDRATE_THEME": { + let collapsed: any = localStorage.getItem("collapsed"); + collapsed = collapsed ? JSON.parse(collapsed) : false; + return { ...initialState, ...payload, collapsed }; + } + case "SET_ISSUE_VIEW": { const newState = { ...state, @@ -260,6 +267,13 @@ export const IssueViewContextProvider: React.FC<{ children: React.ReactNode }> = saveDataToServer(workspaceSlug as string, projectId as string, myViewProps?.default_props); }, [projectId, workspaceSlug, myViewProps]); + useEffect(() => { + dispatch({ + type: "REHYDRATE_THEME", + payload: myViewProps?.view_props, + }); + }, [myViewProps]); + return ( Date: Mon, 30 Jan 2023 19:37:25 +0530 Subject: [PATCH 18/31] fix: shortcut ui, Issue title resizing, comment resizing, kanban scroll fixes (#206) * fix: help shortcut ui fix * fix: issue title resizing ui fix * fix: issue comment resizing ui fix * feat: circular progress bar added * feat: module card delete added * fix: kanban view scroll fix * chore: shortcut command updated * fix: shortcut ui fix --------- Co-authored-by: Anmol Singh Bhatia --- apps/app/components/command-palette/index.tsx | 58 ++--- .../components/command-palette/shortcuts.tsx | 16 +- .../common/bulk-delete-issues-modal.tsx | 5 +- .../common/existing-issues-list-modal.tsx | 5 +- .../components/issues/description-form.tsx | 24 +- .../project/cycles/board-view/index.tsx | 2 +- .../cycles/cycle-detail-sidebar/index.tsx | 11 +- .../project/cycles/stats-view/index.tsx | 2 +- .../project/issues/BoardView/index.tsx | 2 +- .../issue-detail-sidebar/select-blocked.tsx | 5 +- .../issue-detail-sidebar/select-blocker.tsx | 5 +- .../project/issues/issues-list-modal.tsx | 5 +- .../project/modules/board-view/index.tsx | 2 +- .../modules/module-detail-sidebar/index.tsx | 11 +- .../project/modules/single-module-card.tsx | 209 ++++++++++-------- .../app/components/rich-text-editor/index.tsx | 4 +- .../app/components/workspace/help-section.tsx | 2 +- apps/app/layouts/navbar/main-sidebar.tsx | 2 +- apps/app/package.json | 1 + apps/app/pages/[workspaceSlug]/index.tsx | 3 +- .../pages/[workspaceSlug]/me/my-issues.tsx | 5 +- .../projects/[projectId]/cycles/index.tsx | 4 +- .../projects/[projectId]/issues/index.tsx | 4 +- .../projects/[projectId]/modules/index.tsx | 4 +- .../pages/[workspaceSlug]/projects/index.tsx | 5 +- apps/app/styles/editor.css | 4 - pnpm-lock.yaml | 10 + 27 files changed, 232 insertions(+), 178 deletions(-) diff --git a/apps/app/components/command-palette/index.tsx b/apps/app/components/command-palette/index.tsx index c4deb7d5b..9e6dfc860 100644 --- a/apps/app/components/command-palette/index.tsx +++ b/apps/app/components/command-palette/index.tsx @@ -97,32 +97,38 @@ const CommandPalette: React.FC = () => { const handleKeyDown = useCallback( (e: KeyboardEvent) => { - if ((e.ctrlKey || e.metaKey) && e.key === "/") { - e.preventDefault(); - setIsPaletteOpen(true); - } else if ((e.ctrlKey || e.metaKey) && e.key === "i") { - e.preventDefault(); - setIsIssueModalOpen(true); - } else if ((e.ctrlKey || e.metaKey) && e.key === "p") { - e.preventDefault(); - setIsProjectModalOpen(true); - } else if ((e.ctrlKey || e.metaKey) && e.key === "b") { - e.preventDefault(); - toggleCollapsed(); - } else if ((e.ctrlKey || e.metaKey) && e.key === "h") { - e.preventDefault(); - setIsShortcutsModalOpen(true); - } else if ((e.ctrlKey || e.metaKey) && e.key === "q") { - e.preventDefault(); - setIsCreateCycleModalOpen(true); - } else if ((e.ctrlKey || e.metaKey) && e.key === "m") { - e.preventDefault(); - setIsCreateModuleModalOpen(true); - } else if ((e.ctrlKey || e.metaKey) && e.key === "d") { - e.preventDefault(); - setIsBulkDeleteIssuesModalOpen(true); - } else if ((e.ctrlKey || e.metaKey) && e.altKey && e.key === "c") { - e.preventDefault(); + if ( + !(e.target instanceof HTMLTextAreaElement) && + !(e.target instanceof HTMLInputElement) && + !(e.target as Element).classList?.contains("remirror-editor") + ) { + if ((e.ctrlKey || e.metaKey) && e.key === "k") { + e.preventDefault(); + setIsPaletteOpen(true); + } else if (e.key === "c") { + e.preventDefault(); + setIsIssueModalOpen(true); + } else if (e.key === "p") { + e.preventDefault(); + setIsProjectModalOpen(true); + } else if ((e.ctrlKey || e.metaKey) && e.key === "b") { + e.preventDefault(); + toggleCollapsed(); + } else if (e.key === "h") { + e.preventDefault(); + setIsShortcutsModalOpen(true); + } else if (e.key === "q") { + e.preventDefault(); + setIsCreateCycleModalOpen(true); + } else if (e.key === "m") { + e.preventDefault(); + setIsCreateModuleModalOpen(true); + } else if (e.key === "Delete") { + e.preventDefault(); + setIsBulkDeleteIssuesModalOpen(true); + } else if ((e.ctrlKey || e.metaKey) && e.altKey && e.key === "c") { + e.preventDefault(); + } if (!router.query.issueId) return; diff --git a/apps/app/components/command-palette/shortcuts.tsx b/apps/app/components/command-palette/shortcuts.tsx index 39dc7bcff..d073b7076 100644 --- a/apps/app/components/command-palette/shortcuts.tsx +++ b/apps/app/components/command-palette/shortcuts.tsx @@ -15,7 +15,7 @@ const shortcuts = [ { title: "Navigation", shortcuts: [ - { keys: "ctrl,/", description: "To open navigator" }, + { keys: "ctrl,cmd,k", description: "To open navigator" }, { keys: "↑", description: "Move up" }, { keys: "↓", description: "Move down" }, { keys: "←", description: "Move left" }, @@ -27,14 +27,14 @@ const shortcuts = [ { title: "Common", shortcuts: [ - { keys: "ctrl,p", description: "To create project" }, - { keys: "ctrl,i", description: "To create issue" }, - { keys: "ctrl,q", description: "To create cycle" }, - { keys: "ctrl,m", description: "To create module" }, - { keys: "ctrl,d", description: "To bulk delete issues" }, - { keys: "ctrl,h", description: "To open shortcuts guide" }, + { keys: "p", description: "To create project" }, + { keys: "c", description: "To create issue" }, + { keys: "q", description: "To create cycle" }, + { keys: "m", description: "To create module" }, + { keys: "Delete", description: "To bulk delete issues" }, + { keys: "h", description: "To open shortcuts guide" }, { - keys: "ctrl,alt,c", + keys: "ctrl,cmd,alt,c", description: "To copy issue url when on issue detail page.", }, ], diff --git a/apps/app/components/common/bulk-delete-issues-modal.tsx b/apps/app/components/common/bulk-delete-issues-modal.tsx index e46f7eea8..8b8125b48 100644 --- a/apps/app/components/common/bulk-delete-issues-modal.tsx +++ b/apps/app/components/common/bulk-delete-issues-modal.tsx @@ -214,10 +214,7 @@ const BulkDeleteIssuesModal: React.FC = ({ isOpen, setIsOpen }) => {

No issues found. Create a new issue with{" "} -
-                            Ctrl/Command + I
-                          
- . +
C
.

)} diff --git a/apps/app/components/common/existing-issues-list-modal.tsx b/apps/app/components/common/existing-issues-list-modal.tsx index c87508e55..5179facd4 100644 --- a/apps/app/components/common/existing-issues-list-modal.tsx +++ b/apps/app/components/common/existing-issues-list-modal.tsx @@ -189,10 +189,7 @@ const ExistingIssuesListModal: React.FC = ({

No issues found. Create a new issue with{" "} -
-                                  Ctrl/Command + I
-                                
- . +
C
.

)} diff --git a/apps/app/components/issues/description-form.tsx b/apps/app/components/issues/description-form.tsx index 441efedc5..4a484d819 100644 --- a/apps/app/components/issues/description-form.tsx +++ b/apps/app/components/issues/description-form.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useState } from "react"; +import { FC, useEffect, useRef, useState } from "react"; import dynamic from "next/dynamic"; // types import { IIssue } from "types"; @@ -33,10 +33,10 @@ export const IssueDescriptionForm: FC = ({ issue, handleSubmi // description: issue?.description, // description_html: issue?.description_html, // }); - const [issueName, setIssueName] = useState(issue?.name); const [issueDescription, setIssueDescription] = useState(issue?.description); const [issueDescriptionHTML, setIssueDescriptionHTML] = useState(issue?.description_html); + const textareaRef = useRef(null); // hooks const formValues = useDebounce( @@ -50,24 +50,34 @@ export const IssueDescriptionForm: FC = ({ issue, handleSubmi // eslint-disable-next-line react-hooks/exhaustive-deps }, [handleSubmit, stringFromValues]); + useEffect(() => { + if (textareaRef && textareaRef.current) { + textareaRef.current.style.height = "0px"; + const scrollHeight = textareaRef.current.scrollHeight; + textareaRef.current.style.height = scrollHeight + "px"; + } + }, [issueName]); + return (
- setIssueName(e.target.value)} - mode="transparent" - className="text-xl font-medium" + ref={textareaRef} + rows={1} + onChange={(e: React.ChangeEvent) => setIssueName(e.target.value)} required={true} + className="no-scrollbar w-full px-3 py-2 outline-none rounded border-none bg-transparent ring-0 transition-all focus:ring-1 focus:ring-theme text-xl font-medium resize-none" /> + setIssueDescription(json)} onHTMLChange={(html) => setIssueDescriptionHTML(html)} + customClassName="min-h-[150px]" />
); diff --git a/apps/app/components/project/cycles/board-view/index.tsx b/apps/app/components/project/cycles/board-view/index.tsx index a87642f30..ee37b9c75 100644 --- a/apps/app/components/project/cycles/board-view/index.tsx +++ b/apps/app/components/project/cycles/board-view/index.tsx @@ -126,7 +126,7 @@ const CyclesBoardView: React.FC = ({ return ( <> {groupedByIssues ? ( -
+
diff --git a/apps/app/components/project/cycles/cycle-detail-sidebar/index.tsx b/apps/app/components/project/cycles/cycle-detail-sidebar/index.tsx index 08ef636dd..3fa73c913 100644 --- a/apps/app/components/project/cycles/cycle-detail-sidebar/index.tsx +++ b/apps/app/components/project/cycles/cycle-detail-sidebar/index.tsx @@ -14,6 +14,9 @@ import cyclesService from "services/cycles.service"; import useToast from "hooks/use-toast"; // ui import { Loader } from "components/ui"; +//progress-bar +import { CircularProgressbar } from "react-circular-progressbar"; +import "react-circular-progressbar/dist/styles.css"; // helpers import { copyTextToClipboard } from "helpers/string.helper"; import { groupBy } from "helpers/array.helper"; @@ -135,7 +138,13 @@ const CycleDetailSidebar: React.FC = ({ cycle, isOpen, cycleIssues }) =>
- + + +
{groupedIssues.completed.length}/{cycleIssues?.length}
diff --git a/apps/app/components/project/cycles/stats-view/index.tsx b/apps/app/components/project/cycles/stats-view/index.tsx index 4c7ea59ba..58f316464 100644 --- a/apps/app/components/project/cycles/stats-view/index.tsx +++ b/apps/app/components/project/cycles/stats-view/index.tsx @@ -64,7 +64,7 @@ const CycleStatsView: React.FC = ({ )}

No {type} {type === "current" ? "cycle" : "cycles"} yet. Create with{" "} -
Ctrl/Command + Q
. +
Q
.

)} diff --git a/apps/app/components/project/issues/BoardView/index.tsx b/apps/app/components/project/issues/BoardView/index.tsx index 71d5b3f3d..a2f9c04d2 100644 --- a/apps/app/components/project/issues/BoardView/index.tsx +++ b/apps/app/components/project/issues/BoardView/index.tsx @@ -200,7 +200,7 @@ const BoardView: React.FC = ({ issues, handleDeleteIssue, userAuth }) => }} /> {groupedByIssues ? ( -
+
diff --git a/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-blocked.tsx b/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-blocked.tsx index 02a3fa299..1c5822e7e 100644 --- a/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-blocked.tsx +++ b/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-blocked.tsx @@ -259,10 +259,7 @@ const SelectBlocked: React.FC = ({ submitChanges, issuesList, watch }) =>

No issues found. Create a new issue with{" "} -
-                                Ctrl/Command + I
-                              
- . +
C
.

)} diff --git a/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-blocker.tsx b/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-blocker.tsx index 9df9e8266..e2cb85e6f 100644 --- a/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-blocker.tsx +++ b/apps/app/components/project/issues/issue-detail/issue-detail-sidebar/select-blocker.tsx @@ -258,10 +258,7 @@ const SelectBlocker: React.FC = ({ submitChanges, issuesList, watch }) =>

No issues found. Create a new issue with{" "} -
-                              Ctrl/Command + I
-                            
- . +
C
.

)} diff --git a/apps/app/components/project/issues/issues-list-modal.tsx b/apps/app/components/project/issues/issues-list-modal.tsx index c453b1fea..9c56d6406 100644 --- a/apps/app/components/project/issues/issues-list-modal.tsx +++ b/apps/app/components/project/issues/issues-list-modal.tsx @@ -212,10 +212,7 @@ const IssuesListModal: React.FC = ({

No issues found. Create a new issue with{" "} -
-                              Ctrl/Command + I
-                            
- . +
C
.

)} diff --git a/apps/app/components/project/modules/board-view/index.tsx b/apps/app/components/project/modules/board-view/index.tsx index a8cc1644e..4951f80af 100644 --- a/apps/app/components/project/modules/board-view/index.tsx +++ b/apps/app/components/project/modules/board-view/index.tsx @@ -129,7 +129,7 @@ const ModulesBoardView: React.FC = ({ return ( <> {groupedByIssues ? ( -
+
diff --git a/apps/app/components/project/modules/module-detail-sidebar/index.tsx b/apps/app/components/project/modules/module-detail-sidebar/index.tsx index 0c29f9936..a58816f6a 100644 --- a/apps/app/components/project/modules/module-detail-sidebar/index.tsx +++ b/apps/app/components/project/modules/module-detail-sidebar/index.tsx @@ -22,6 +22,9 @@ import SelectLead from "components/project/modules/module-detail-sidebar/select- import SelectMembers from "components/project/modules/module-detail-sidebar/select-members"; import SelectStatus from "components/project/modules/module-detail-sidebar/select-status"; import ModuleLinkModal from "components/project/modules/module-link-modal"; +//progress-bar +import { CircularProgressbar } from "react-circular-progressbar"; +import "react-circular-progressbar/dist/styles.css"; // ui import { Loader } from "components/ui"; // icons @@ -161,7 +164,13 @@ const ModuleDetailSidebar: React.FC = ({
- + + +
{groupedIssues.completed.length}/{moduleIssues?.length}
diff --git a/apps/app/components/project/modules/single-module-card.tsx b/apps/app/components/project/modules/single-module-card.tsx index ec151f242..7af928b6a 100644 --- a/apps/app/components/project/modules/single-module-card.tsx +++ b/apps/app/components/project/modules/single-module-card.tsx @@ -1,14 +1,15 @@ -import React from "react"; +import React, { useState } from "react"; import Link from "next/link"; import Image from "next/image"; import { useRouter } from "next/router"; +import { CalendarDaysIcon, TrashIcon } from "@heroicons/react/24/outline"; +import ConfirmModuleDeletion from "./confirm-module-deletion"; // icons -import { CalendarDaysIcon } from "@heroicons/react/24/outline"; import User from "public/user.png"; // helpers import { renderShortNumericDateFormat } from "helpers/date-time.helper"; // types -import { IModule } from "types"; +import { IModule, SelectModuleType } from "types"; // common import { MODULE_STATUS } from "constants/"; @@ -19,103 +20,131 @@ type Props = { const SingleModuleCard: React.FC = ({ module }) => { const router = useRouter(); const { workspaceSlug } = router.query; + const [moduleDeleteModal, setModuleDeleteModal] = useState(false); + const [selectedModuleForDelete, setSelectedModuleForDelete] = useState(); + const handleDeleteModule = () => { + if (!module) return; + + setSelectedModuleForDelete({ ...module, actionType: "delete" }); + setModuleDeleteModal(true); + }; return ( - - - {module.name} -
-
-
LEAD
-
- {module.lead ? ( - module.lead_detail?.avatar && module.lead_detail.avatar !== "" ? ( -
+
+
+ +
+ + +
+ {module.name} +
+
+
LEAD
+
+ {module.lead ? ( + module.lead_detail?.avatar && module.lead_detail.avatar !== "" ? ( +
+ {module.lead_detail.first_name} +
+ ) : ( +
+ {module.lead_detail?.first_name && module.lead_detail.first_name !== "" + ? module.lead_detail.first_name.charAt(0) + : module.lead_detail?.email.charAt(0)} +
+ ) + ) : ( + "N/A" + )} +
+
+
+
MEMBERS
+
+ {module.members && module.members.length > 0 ? ( + module?.members_detail?.map((member, index: number) => ( +
+ {member?.avatar && member.avatar !== "" ? ( +
+ {member?.first_name} +
+ ) : ( +
+ {member?.first_name && member.first_name !== "" + ? member.first_name.charAt(0) + : member?.email?.charAt(0)} +
+ )} +
+ )) + ) : ( +
{module.lead_detail.first_name}
- ) : ( -
- {module.lead_detail?.first_name && module.lead_detail.first_name !== "" - ? module.lead_detail.first_name.charAt(0) - : module.lead_detail?.email.charAt(0)} -
- ) - ) : ( - "N/A" - )} + )} +
+
+
+
END DATE
+
+ + {renderShortNumericDateFormat(module.target_date ?? "")} +
+
+
+
STATUS
+
+ s.value === module.status)?.color, + }} + /> + {module.status} +
-
-
MEMBERS
-
- {module.members && module.members.length > 0 ? ( - module?.members_detail?.map((member, index: number) => ( -
- {member?.avatar && member.avatar !== "" ? ( -
- {member?.first_name} -
- ) : ( -
- {member?.first_name && member.first_name !== "" - ? member.first_name.charAt(0) - : member?.email?.charAt(0)} -
- )} -
- )) - ) : ( -
- No user -
- )} -
-
-
-
END DATE
-
- - {renderShortNumericDateFormat(module.target_date ?? "")} -
-
-
-
STATUS
-
- s.value === module.status)?.color, - }} - /> - {module.status} -
-
-
- - + + +
); }; diff --git a/apps/app/components/rich-text-editor/index.tsx b/apps/app/components/rich-text-editor/index.tsx index cb3796c83..2bddfe5f6 100644 --- a/apps/app/components/rich-text-editor/index.tsx +++ b/apps/app/components/rich-text-editor/index.tsx @@ -47,6 +47,7 @@ export interface IRemirrorRichTextEditor { value?: any; showToolbar?: boolean; editable?: boolean; + customClassName?: string; } const RemirrorRichTextEditor: FC = (props) => { @@ -60,6 +61,7 @@ const RemirrorRichTextEditor: FC = (props) => { value = "", showToolbar = true, editable = true, + customClassName, } = props; const [imageLoader, setImageLoader] = useState(false); @@ -173,7 +175,7 @@ const RemirrorRichTextEditor: FC = (props) => { { onBlur(jsonValue, htmlValue); diff --git a/apps/app/components/workspace/help-section.tsx b/apps/app/components/workspace/help-section.tsx index dbb422998..2122b37e7 100644 --- a/apps/app/components/workspace/help-section.tsx +++ b/apps/app/components/workspace/help-section.tsx @@ -132,7 +132,7 @@ export const WorkspaceHelpSection: FC = (props) => { title="Help" > - {!sidebarCollapse && Need help?} + {!sidebarCollapse && Help ?}
diff --git a/apps/app/layouts/navbar/main-sidebar.tsx b/apps/app/layouts/navbar/main-sidebar.tsx index 834c3f58c..4afddbe90 100644 --- a/apps/app/layouts/navbar/main-sidebar.tsx +++ b/apps/app/layouts/navbar/main-sidebar.tsx @@ -173,7 +173,7 @@ const Sidebar: React.FC = ({ toggleSidebar, setToggleSidebar }) => { title="Help" > - {!sidebarCollapse && Need help?} + {!sidebarCollapse && Help ?}
diff --git a/apps/app/package.json b/apps/app/package.json index 5051a01e3..b93c298df 100644 --- a/apps/app/package.json +++ b/apps/app/package.json @@ -21,6 +21,7 @@ "next-pwa": "^5.6.0", "react": "18.2.0", "react-beautiful-dnd": "^13.1.1", + "react-circular-progressbar": "^2.1.0", "react-color": "^2.19.3", "react-dom": "18.2.0", "react-dropzone": "^14.2.3", diff --git a/apps/app/pages/[workspaceSlug]/index.tsx b/apps/app/pages/[workspaceSlug]/index.tsx index 17abe8e77..4ce65369b 100644 --- a/apps/app/pages/[workspaceSlug]/index.tsx +++ b/apps/app/pages/[workspaceSlug]/index.tsx @@ -164,8 +164,7 @@ const WorkspacePage: NextPage = () => {

No issues found. Create a new issue with{" "} -
Ctrl/Command + I
- . +
C
.

) diff --git a/apps/app/pages/[workspaceSlug]/me/my-issues.tsx b/apps/app/pages/[workspaceSlug]/me/my-issues.tsx index a1638ed9b..4c47a47bd 100644 --- a/apps/app/pages/[workspaceSlug]/me/my-issues.tsx +++ b/apps/app/pages/[workspaceSlug]/me/my-issues.tsx @@ -163,9 +163,8 @@ const MyIssuesPage: NextPage = () => { title="Create a new issue" description={ - Use{" "} -
Ctrl/Command + I
{" "} - shortcut to create a new issue + Use
C
shortcut + to create a new issue
} Icon={PlusIcon} diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx index 269792f1b..029a1d785 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx @@ -178,8 +178,8 @@ const ProjectCycles: NextPage = () => { title="Create a new cycle" description={ - Use
Ctrl/Command + Q
{" "} - shortcut to create a new cycle + Use
Q
shortcut to + create a new cycle
} Icon={PlusIcon} diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx index 4348ebb63..0bfc2e299 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/issues/index.tsx @@ -133,8 +133,8 @@ const ProjectIssues: NextPage = (props) => { title="Create a new issue" description={ - Use
Ctrl/Command + I
{" "} - shortcut to create a new issue + Use
C
shortcut to + create a new issue
} Icon={PlusIcon} diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx index 8aa1056c6..25b2bdddb 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/modules/index.tsx @@ -86,8 +86,8 @@ const ProjectModules: NextPage = () => { title="Create a new module" description={ - Use
Ctrl/Command + M
{" "} - shortcut to create a new module + Use
M
shortcut to + create a new module
} Icon={PlusIcon} diff --git a/apps/app/pages/[workspaceSlug]/projects/index.tsx b/apps/app/pages/[workspaceSlug]/projects/index.tsx index 700bffd5a..8164d4155 100644 --- a/apps/app/pages/[workspaceSlug]/projects/index.tsx +++ b/apps/app/pages/[workspaceSlug]/projects/index.tsx @@ -88,9 +88,8 @@ const ProjectsPage: NextPage = () => { title="Create a new project" description={ - Use{" "} -
Ctrl/Command + P
{" "} - shortcut to create a new project + Use
P
shortcut to + create a new project
} Icon={PlusIcon} diff --git a/apps/app/styles/editor.css b/apps/app/styles/editor.css index 45deb0dc6..07fcec355 100644 --- a/apps/app/styles/editor.css +++ b/apps/app/styles/editor.css @@ -354,10 +354,6 @@ img.ProseMirror-separator { margin-bottom: 1em; } -.remirror-editor-wrapper .remirror-editor { - min-height: 150px; -} - .issue-comments-section .remirror-editor-wrapper .remirror-editor { min-height: 50px; } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3cee54a85..3095d80ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -40,6 +40,7 @@ importers: postcss: ^8.4.14 react: 18.2.0 react-beautiful-dnd: ^13.1.1 + react-circular-progressbar: ^2.1.0 react-color: ^2.19.3 react-dom: 18.2.0 react-dropzone: ^14.2.3 @@ -63,6 +64,7 @@ importers: next-pwa: 5.6.0_next@12.3.2 react: 18.2.0 react-beautiful-dnd: 13.1.1_biqbaboplfbrettd7655fr4n2y + react-circular-progressbar: 2.1.0_react@18.2.0 react-color: 2.19.3_react@18.2.0 react-dom: 18.2.0_react@18.2.0 react-dropzone: 14.2.3_react@18.2.0 @@ -8326,6 +8328,14 @@ packages: - react-native dev: false + /react-circular-progressbar/2.1.0_react@18.2.0: + resolution: {integrity: sha512-xp4THTrod4aLpGy68FX/k1Q3nzrfHUjUe5v6FsdwXBl3YVMwgeXYQKDrku7n/D6qsJA9CuunarAboC2xCiKs1g==} + peerDependencies: + react: ^0.14.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + dev: false + /react-color/2.19.3_react@18.2.0: resolution: {integrity: sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==} peerDependencies: From fcf23b985bdd24bb05231e5f307c7b96edbc161d Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Mon, 30 Jan 2023 19:47:30 +0530 Subject: [PATCH 19/31] fix: issue description debounce issue (#208) * fix: issue description form * fix: build errors --- .../components/issues/description-form.tsx | 102 ++++++++++-------- apps/app/components/issues/form.tsx | 2 +- .../issue-detail-sidebar/index.tsx | 21 ++++ apps/app/package.json | 4 + .../projects/[projectId]/issues/[issueId].tsx | 24 +---- pnpm-lock.yaml | 86 ++++++++++++++- 6 files changed, 172 insertions(+), 67 deletions(-) diff --git a/apps/app/components/issues/description-form.tsx b/apps/app/components/issues/description-form.tsx index 4a484d819..d3f5433cc 100644 --- a/apps/app/components/issues/description-form.tsx +++ b/apps/app/components/issues/description-form.tsx @@ -1,7 +1,11 @@ -import { FC, useEffect, useRef, useState } from "react"; +import { FC, useCallback, useEffect, useMemo } from "react"; + import dynamic from "next/dynamic"; -// types -import { IIssue } from "types"; + +// react-hook-form +import { useForm } from "react-hook-form"; +// lodash +import debounce from "lodash.debounce"; // components import { Loader, Input } from "components/ui"; const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor"), { @@ -12,8 +16,8 @@ const RemirrorRichTextEditor = dynamic(() => import("components/rich-text-editor ), }); -// hooks -import useDebounce from "hooks/use-debounce"; +// types +import { IIssue } from "types"; export interface IssueDescriptionFormValues { name: string; @@ -23,61 +27,73 @@ export interface IssueDescriptionFormValues { export interface IssueDetailsProps { issue: IIssue; - handleSubmit: (value: IssueDescriptionFormValues) => void; + handleFormSubmit: (value: IssueDescriptionFormValues) => void; } -export const IssueDescriptionForm: FC = ({ issue, handleSubmit }) => { - // states - // const [issueFormValues, setIssueFormValues] = useState({ - // name: issue.name, - // description: issue?.description, - // description_html: issue?.description_html, - // }); - const [issueName, setIssueName] = useState(issue?.name); - const [issueDescription, setIssueDescription] = useState(issue?.description); - const [issueDescriptionHTML, setIssueDescriptionHTML] = useState(issue?.description_html); - const textareaRef = useRef(null); +export const IssueDescriptionForm: FC = ({ issue, handleFormSubmit }) => { + const { handleSubmit, watch, setValue, reset } = useForm({ + defaultValues: { + name: "", + description: "", + description_html: "", + }, + }); - // hooks - const formValues = useDebounce( - { name: issueName, description: issueDescription, description_html: issueDescriptionHTML }, - 2000 + const handleDescriptionFormSubmit = useCallback( + (formData: Partial) => { + handleFormSubmit({ + name: formData.name ?? "", + description: formData.description, + description_html: formData.description_html, + }); + }, + [handleFormSubmit] ); - const stringFromValues = JSON.stringify(formValues); - useEffect(() => { - handleSubmit(formValues); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [handleSubmit, stringFromValues]); + const debounceHandler = useMemo( + () => debounce(handleSubmit(handleDescriptionFormSubmit), 2000), + [handleSubmit, handleDescriptionFormSubmit] + ); + useEffect( + () => () => { + debounceHandler.cancel(); + }, + [debounceHandler] + ); + + // reset form values useEffect(() => { - if (textareaRef && textareaRef.current) { - textareaRef.current.style.height = "0px"; - const scrollHeight = textareaRef.current.scrollHeight; - textareaRef.current.style.height = scrollHeight + "px"; - } - }, [issueName]); + if (!issue) return; + + reset(issue); + }, [issue, reset]); return (
-