diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index 4f895aeba..e84b6dd0a 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -80,7 +80,7 @@ class CycleViewSet(BaseViewSet): issue_id=str(self.kwargs.get("pk", None)), project_id=str(self.kwargs.get("project_id", None)), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return super().perform_destroy(instance) @@ -102,48 +102,84 @@ class CycleViewSet(BaseViewSet): .select_related("workspace") .select_related("owned_by") .annotate(is_favorite=Exists(subquery)) - .annotate(total_issues=Count("issue_cycle")) + .annotate( + total_issues=Count( + "issue_cycle", + filter=Q( + issue_cycle__issue__archived_at__isnull=True, + issue_cycle__issue__is_draft=False, + ), + ) + ) .annotate( completed_issues=Count( "issue_cycle__issue__state__group", - filter=Q(issue_cycle__issue__state__group="completed"), + filter=Q( + issue_cycle__issue__state__group="completed", + issue_cycle__issue__archived_at__isnull=True, + issue_cycle__issue__is_draft=False, + ), ) ) .annotate( cancelled_issues=Count( "issue_cycle__issue__state__group", - filter=Q(issue_cycle__issue__state__group="cancelled"), + filter=Q( + issue_cycle__issue__state__group="cancelled", + issue_cycle__issue__archived_at__isnull=True, + issue_cycle__issue__is_draft=False, + ), ) ) .annotate( started_issues=Count( "issue_cycle__issue__state__group", - filter=Q(issue_cycle__issue__state__group="started"), + filter=Q( + issue_cycle__issue__state__group="started", + issue_cycle__issue__archived_at__isnull=True, + issue_cycle__issue__is_draft=False, + ), ) ) .annotate( unstarted_issues=Count( "issue_cycle__issue__state__group", - filter=Q(issue_cycle__issue__state__group="unstarted"), + filter=Q( + issue_cycle__issue__state__group="unstarted", + issue_cycle__issue__archived_at__isnull=True, + issue_cycle__issue__is_draft=False, + ), ) ) .annotate( backlog_issues=Count( "issue_cycle__issue__state__group", - filter=Q(issue_cycle__issue__state__group="backlog"), + filter=Q( + issue_cycle__issue__state__group="backlog", + issue_cycle__issue__archived_at__isnull=True, + issue_cycle__issue__is_draft=False, + ), ) ) .annotate(total_estimates=Sum("issue_cycle__issue__estimate_point")) .annotate( completed_estimates=Sum( "issue_cycle__issue__estimate_point", - filter=Q(issue_cycle__issue__state__group="completed"), + filter=Q( + issue_cycle__issue__state__group="completed", + issue_cycle__issue__archived_at__isnull=True, + issue_cycle__issue__is_draft=False, + ), ) ) .annotate( started_estimates=Sum( "issue_cycle__issue__estimate_point", - filter=Q(issue_cycle__issue__state__group="started"), + filter=Q( + issue_cycle__issue__state__group="started", + issue_cycle__issue__archived_at__isnull=True, + issue_cycle__issue__is_draft=False, + ), ) ) .prefetch_related( @@ -196,17 +232,30 @@ class CycleViewSet(BaseViewSet): .annotate(assignee_id=F("assignees__id")) .annotate(avatar=F("assignees__avatar")) .values("display_name", "assignee_id", "avatar") - .annotate(total_issues=Count("assignee_id")) + .annotate( + total_issues=Count( + "assignee_id", + filter=Q(archived_at__isnull=True, is_draft=False), + ), + ) .annotate( completed_issues=Count( "assignee_id", - filter=Q(completed_at__isnull=False), + filter=Q( + completed_at__isnull=False, + archived_at__isnull=True, + is_draft=False, + ), ) ) .annotate( pending_issues=Count( "assignee_id", - filter=Q(completed_at__isnull=True), + filter=Q( + completed_at__isnull=True, + archived_at__isnull=True, + is_draft=False, + ), ) ) .order_by("display_name") @@ -222,17 +271,30 @@ class CycleViewSet(BaseViewSet): .annotate(color=F("labels__color")) .annotate(label_id=F("labels__id")) .values("label_name", "color", "label_id") - .annotate(total_issues=Count("label_id")) + .annotate( + total_issues=Count( + "label_id", + filter=Q(archived_at__isnull=True, is_draft=False), + ) + ) .annotate( completed_issues=Count( "label_id", - filter=Q(completed_at__isnull=False), + filter=Q( + completed_at__isnull=False, + archived_at__isnull=True, + is_draft=False, + ), ) ) .annotate( pending_issues=Count( "label_id", - filter=Q(completed_at__isnull=True), + filter=Q( + completed_at__isnull=True, + archived_at__isnull=True, + is_draft=False, + ), ) ) .order_by("label_name") @@ -385,17 +447,30 @@ class CycleViewSet(BaseViewSet): .values( "first_name", "last_name", "assignee_id", "avatar", "display_name" ) - .annotate(total_issues=Count("assignee_id")) + .annotate( + total_issues=Count( + "assignee_id", + filter=Q(archived_at__isnull=True, is_draft=False), + ), + ) .annotate( completed_issues=Count( "assignee_id", - filter=Q(completed_at__isnull=False), + filter=Q( + completed_at__isnull=False, + archived_at__isnull=True, + is_draft=False, + ), ) ) .annotate( pending_issues=Count( "assignee_id", - filter=Q(completed_at__isnull=True), + filter=Q( + completed_at__isnull=True, + archived_at__isnull=True, + is_draft=False, + ), ) ) .order_by("first_name", "last_name") @@ -412,17 +487,30 @@ class CycleViewSet(BaseViewSet): .annotate(color=F("labels__color")) .annotate(label_id=F("labels__id")) .values("label_name", "color", "label_id") - .annotate(total_issues=Count("label_id")) + .annotate( + total_issues=Count( + "label_id", + filter=Q(archived_at__isnull=True, is_draft=False), + ), + ) .annotate( completed_issues=Count( "label_id", - filter=Q(completed_at__isnull=False), + filter=Q( + completed_at__isnull=False, + archived_at__isnull=True, + is_draft=False, + ), ) ) .annotate( pending_issues=Count( "label_id", - filter=Q(completed_at__isnull=True), + filter=Q( + completed_at__isnull=True, + archived_at__isnull=True, + is_draft=False, + ), ) ) .order_by("label_name") @@ -488,7 +576,7 @@ class CycleIssueViewSet(BaseViewSet): issue_id=str(self.kwargs.get("pk", None)), project_id=str(self.kwargs.get("project_id", None)), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return super().perform_destroy(instance) @@ -664,7 +752,7 @@ class CycleIssueViewSet(BaseViewSet): ), } ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) # Return all Cycle Issues diff --git a/apiserver/plane/api/views/importer.py b/apiserver/plane/api/views/importer.py index 0a92b3850..18d9a1d69 100644 --- a/apiserver/plane/api/views/importer.py +++ b/apiserver/plane/api/views/importer.py @@ -384,7 +384,7 @@ class BulkImportIssuesEndpoint(BaseAPIView): sort_order=largest_sort_order, start_date=issue_data.get("start_date", None), target_date=issue_data.get("target_date", None), - priority=issue_data.get("priority", None), + priority=issue_data.get("priority", "none"), created_by=request.user, ) ) diff --git a/apiserver/plane/api/views/inbox.py b/apiserver/plane/api/views/inbox.py index 1a0284ea4..79294275e 100644 --- a/apiserver/plane/api/views/inbox.py +++ b/apiserver/plane/api/views/inbox.py @@ -173,12 +173,12 @@ class InboxIssueViewSet(BaseViewSet): ) # Check for valid priority - if not request.data.get("issue", {}).get("priority", None) in [ + if not request.data.get("issue", {}).get("priority", "none") in [ "low", "medium", "high", "urgent", - None, + "none", ]: return Response( {"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST @@ -213,7 +213,7 @@ class InboxIssueViewSet(BaseViewSet): issue_id=str(issue.id), project_id=str(project_id), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) # create an inbox issue InboxIssue.objects.create( @@ -278,7 +278,7 @@ class InboxIssueViewSet(BaseViewSet): IssueSerializer(current_instance).data, cls=DjangoJSONEncoder, ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) issue_serializer.save() else: @@ -480,12 +480,12 @@ class InboxIssuePublicViewSet(BaseViewSet): ) # Check for valid priority - if not request.data.get("issue", {}).get("priority", None) in [ + if not request.data.get("issue", {}).get("priority", "none") in [ "low", "medium", "high", "urgent", - None, + "none", ]: return Response( {"error": "Invalid priority"}, status=status.HTTP_400_BAD_REQUEST @@ -520,7 +520,7 @@ class InboxIssuePublicViewSet(BaseViewSet): issue_id=str(issue.id), project_id=str(project_id), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) # create an inbox issue InboxIssue.objects.create( @@ -585,7 +585,7 @@ class InboxIssuePublicViewSet(BaseViewSet): IssueSerializer(current_instance).data, cls=DjangoJSONEncoder, ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) issue_serializer.save() return Response(issue_serializer.data, status=status.HTTP_200_OK) diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index e653f3d44..003a8ae32 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -130,7 +130,7 @@ class IssueViewSet(BaseViewSet): current_instance=json.dumps( IssueSerializer(current_instance).data, cls=DjangoJSONEncoder ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return super().perform_update(serializer) @@ -151,7 +151,7 @@ class IssueViewSet(BaseViewSet): current_instance=json.dumps( IssueSerializer(current_instance).data, cls=DjangoJSONEncoder ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return super().perform_destroy(instance) @@ -318,7 +318,7 @@ class IssueViewSet(BaseViewSet): issue_id=str(serializer.data.get("id", None)), project_id=str(project_id), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -577,7 +577,7 @@ class IssueCommentViewSet(BaseViewSet): issue_id=str(self.kwargs.get("issue_id")), project_id=str(self.kwargs.get("project_id")), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) def perform_update(self, serializer): @@ -596,7 +596,7 @@ class IssueCommentViewSet(BaseViewSet): IssueCommentSerializer(current_instance).data, cls=DjangoJSONEncoder, ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return super().perform_update(serializer) @@ -618,7 +618,7 @@ class IssueCommentViewSet(BaseViewSet): IssueCommentSerializer(current_instance).data, cls=DjangoJSONEncoder, ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return super().perform_destroy(instance) @@ -902,7 +902,7 @@ class IssueLinkViewSet(BaseViewSet): issue_id=str(self.kwargs.get("issue_id")), project_id=str(self.kwargs.get("project_id")), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) def perform_update(self, serializer): @@ -921,7 +921,7 @@ class IssueLinkViewSet(BaseViewSet): IssueLinkSerializer(current_instance).data, cls=DjangoJSONEncoder, ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return super().perform_update(serializer) @@ -943,7 +943,7 @@ class IssueLinkViewSet(BaseViewSet): IssueLinkSerializer(current_instance).data, cls=DjangoJSONEncoder, ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return super().perform_destroy(instance) @@ -1022,7 +1022,7 @@ class IssueAttachmentEndpoint(BaseAPIView): serializer.data, cls=DjangoJSONEncoder, ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -1045,7 +1045,7 @@ class IssueAttachmentEndpoint(BaseAPIView): issue_id=str(self.kwargs.get("issue_id", None)), project_id=str(self.kwargs.get("project_id", None)), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return Response(status=status.HTTP_204_NO_CONTENT) @@ -1248,7 +1248,7 @@ class IssueArchiveViewSet(BaseViewSet): issue_id=str(issue.id), project_id=str(project_id), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return Response(IssueSerializer(issue).data, status=status.HTTP_200_OK) @@ -1453,7 +1453,7 @@ class IssueReactionViewSet(BaseViewSet): issue_id=str(self.kwargs.get("issue_id", None)), project_id=str(self.kwargs.get("project_id", None)), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) def destroy(self, request, slug, project_id, issue_id, reaction_code): @@ -1477,7 +1477,7 @@ class IssueReactionViewSet(BaseViewSet): "identifier": str(issue_reaction.id), } ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) issue_reaction.delete() return Response(status=status.HTTP_204_NO_CONTENT) @@ -1526,7 +1526,7 @@ class CommentReactionViewSet(BaseViewSet): issue_id=None, project_id=str(self.kwargs.get("project_id", None)), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) def destroy(self, request, slug, project_id, comment_id, reaction_code): @@ -1551,7 +1551,7 @@ class CommentReactionViewSet(BaseViewSet): "comment_id": str(comment_id), } ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) comment_reaction.delete() return Response(status=status.HTTP_204_NO_CONTENT) @@ -1648,7 +1648,7 @@ class IssueCommentPublicViewSet(BaseViewSet): issue_id=str(issue_id), project_id=str(project_id), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) if not ProjectMember.objects.filter( project_id=project_id, @@ -1698,7 +1698,7 @@ class IssueCommentPublicViewSet(BaseViewSet): IssueCommentSerializer(comment).data, cls=DjangoJSONEncoder, ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -1732,7 +1732,7 @@ class IssueCommentPublicViewSet(BaseViewSet): IssueCommentSerializer(comment).data, cls=DjangoJSONEncoder, ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) comment.delete() return Response(status=status.HTTP_204_NO_CONTENT) @@ -1807,7 +1807,7 @@ class IssueReactionPublicViewSet(BaseViewSet): issue_id=str(self.kwargs.get("issue_id", None)), project_id=str(self.kwargs.get("project_id", None)), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -1852,7 +1852,7 @@ class IssueReactionPublicViewSet(BaseViewSet): "identifier": str(issue_reaction.id), } ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) issue_reaction.delete() return Response(status=status.HTTP_204_NO_CONTENT) @@ -1926,7 +1926,7 @@ class CommentReactionPublicViewSet(BaseViewSet): issue_id=None, project_id=str(self.kwargs.get("project_id", None)), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -1978,7 +1978,7 @@ class CommentReactionPublicViewSet(BaseViewSet): "comment_id": str(comment_id), } ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) comment_reaction.delete() return Response(status=status.HTTP_204_NO_CONTENT) @@ -2042,7 +2042,7 @@ class IssueVotePublicViewSet(BaseViewSet): issue_id=str(self.kwargs.get("issue_id", None)), project_id=str(self.kwargs.get("project_id", None)), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) serializer = IssueVoteSerializer(issue_vote) return Response(serializer.data, status=status.HTTP_201_CREATED) @@ -2077,7 +2077,7 @@ class IssueVotePublicViewSet(BaseViewSet): "identifier": str(issue_vote.id), } ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) issue_vote.delete() return Response(status=status.HTTP_204_NO_CONTENT) @@ -2111,7 +2111,7 @@ class IssueRelationViewSet(BaseViewSet): IssueRelationSerializer(current_instance).data, cls=DjangoJSONEncoder, ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return super().perform_destroy(instance) @@ -2145,7 +2145,7 @@ class IssueRelationViewSet(BaseViewSet): issue_id=str(issue_id), project_id=str(project_id), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) if relation == "blocking": @@ -2417,7 +2417,7 @@ class IssueDraftViewSet(BaseViewSet): current_instance=json.dumps( IssueSerializer(current_instance).data, cls=DjangoJSONEncoder ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return super().perform_update(serializer) @@ -2439,6 +2439,7 @@ class IssueDraftViewSet(BaseViewSet): current_instance=json.dumps( IssueSerializer(current_instance).data, cls=DjangoJSONEncoder ), + epoch=int(timezone.now().timestamp()) ) return super().perform_destroy(instance) @@ -2602,7 +2603,7 @@ class IssueDraftViewSet(BaseViewSet): issue_id=str(serializer.data.get("id", None)), project_id=str(project_id), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) diff --git a/apiserver/plane/api/views/module.py b/apiserver/plane/api/views/module.py index c2a15da1c..1489edb2d 100644 --- a/apiserver/plane/api/views/module.py +++ b/apiserver/plane/api/views/module.py @@ -40,6 +40,7 @@ from plane.utils.grouper import group_results from plane.utils.issue_filters import issue_filters from plane.utils.analytics_plot import burndown_plot + class ModuleViewSet(BaseViewSet): model = Module permission_classes = [ @@ -78,35 +79,63 @@ class ModuleViewSet(BaseViewSet): queryset=ModuleLink.objects.select_related("module", "created_by"), ) ) - .annotate(total_issues=Count("issue_module")) + .annotate( + total_issues=Count( + "issue_module", + filter=Q( + issue_module__issue__archived_at__isnull=True, + issue_module__issue__is_draft=False, + ), + ), + ) .annotate( completed_issues=Count( "issue_module__issue__state__group", - filter=Q(issue_module__issue__state__group="completed"), + filter=Q( + issue_module__issue__state__group="completed", + issue_module__issue__archived_at__isnull=True, + issue_module__issue__is_draft=False, + ), ) ) .annotate( cancelled_issues=Count( "issue_module__issue__state__group", - filter=Q(issue_module__issue__state__group="cancelled"), + filter=Q( + issue_module__issue__state__group="cancelled", + issue_module__issue__archived_at__isnull=True, + issue_module__issue__is_draft=False, + ), ) ) .annotate( started_issues=Count( "issue_module__issue__state__group", - filter=Q(issue_module__issue__state__group="started"), + filter=Q( + issue_module__issue__state__group="started", + issue_module__issue__archived_at__isnull=True, + issue_module__issue__is_draft=False, + ), ) ) .annotate( unstarted_issues=Count( "issue_module__issue__state__group", - filter=Q(issue_module__issue__state__group="unstarted"), + filter=Q( + issue_module__issue__state__group="unstarted", + issue_module__issue__archived_at__isnull=True, + issue_module__issue__is_draft=False, + ), ) ) .annotate( backlog_issues=Count( "issue_module__issue__state__group", - filter=Q(issue_module__issue__state__group="backlog"), + filter=Q( + issue_module__issue__state__group="backlog", + issue_module__issue__archived_at__isnull=True, + issue_module__issue__is_draft=False, + ), ) ) .order_by(order_by, "name") @@ -130,7 +159,7 @@ class ModuleViewSet(BaseViewSet): issue_id=str(self.kwargs.get("pk", None)), project_id=str(self.kwargs.get("project_id", None)), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return super().perform_destroy(instance) @@ -179,18 +208,36 @@ class ModuleViewSet(BaseViewSet): .annotate(assignee_id=F("assignees__id")) .annotate(display_name=F("assignees__display_name")) .annotate(avatar=F("assignees__avatar")) - .values("first_name", "last_name", "assignee_id", "avatar", "display_name") - .annotate(total_issues=Count("assignee_id")) + .values( + "first_name", "last_name", "assignee_id", "avatar", "display_name" + ) + .annotate( + total_issues=Count( + "assignee_id", + filter=Q( + archived_at__isnull=True, + is_draft=False, + ), + ) + ) .annotate( completed_issues=Count( "assignee_id", - filter=Q(completed_at__isnull=False), + filter=Q( + completed_at__isnull=False, + archived_at__isnull=True, + is_draft=False, + ), ) ) .annotate( pending_issues=Count( "assignee_id", - filter=Q(completed_at__isnull=True), + filter=Q( + completed_at__isnull=True, + archived_at__isnull=True, + is_draft=False, + ), ) ) .order_by("first_name", "last_name") @@ -206,17 +253,33 @@ class ModuleViewSet(BaseViewSet): .annotate(color=F("labels__color")) .annotate(label_id=F("labels__id")) .values("label_name", "color", "label_id") - .annotate(total_issues=Count("label_id")) + .annotate( + total_issues=Count( + "label_id", + filter=Q( + archived_at__isnull=True, + is_draft=False, + ), + ), + ) .annotate( completed_issues=Count( "label_id", - filter=Q(completed_at__isnull=False), + filter=Q( + completed_at__isnull=False, + archived_at__isnull=True, + is_draft=False, + ), ) ) .annotate( pending_issues=Count( "label_id", - filter=Q(completed_at__isnull=True), + filter=Q( + completed_at__isnull=True, + archived_at__isnull=True, + is_draft=False, + ), ) ) .order_by("label_name") @@ -279,7 +342,7 @@ class ModuleIssueViewSet(BaseViewSet): issue_id=str(self.kwargs.get("pk", None)), project_id=str(self.kwargs.get("project_id", None)), current_instance=None, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return super().perform_destroy(instance) @@ -447,7 +510,7 @@ class ModuleIssueViewSet(BaseViewSet): ), } ), - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) return Response( @@ -494,7 +557,6 @@ class ModuleLinkViewSet(BaseViewSet): class ModuleFavoriteViewSet(BaseViewSet): - serializer_class = ModuleFavoriteSerializer model = ModuleFavorite diff --git a/apiserver/plane/api/views/workspace.py b/apiserver/plane/api/views/workspace.py index 2d1ee8132..753fd861b 100644 --- a/apiserver/plane/api/views/workspace.py +++ b/apiserver/plane/api/views/workspace.py @@ -1239,13 +1239,21 @@ class WorkspaceUserProfileEndpoint(BaseAPIView): .annotate( created_issues=Count( "project_issue", - filter=Q(project_issue__created_by_id=user_id), + filter=Q( + project_issue__created_by_id=user_id, + project_issue__archived_at__isnull=True, + project_issue__is_draft=False, + ), ) ) .annotate( assigned_issues=Count( "project_issue", - filter=Q(project_issue__assignees__in=[user_id]), + filter=Q( + project_issue__assignees__in=[user_id], + project_issue__archived_at__isnull=True, + project_issue__is_draft=False, + ), ) ) .annotate( @@ -1254,6 +1262,8 @@ class WorkspaceUserProfileEndpoint(BaseAPIView): filter=Q( project_issue__completed_at__isnull=False, project_issue__assignees__in=[user_id], + project_issue__archived_at__isnull=True, + project_issue__is_draft=False, ), ) ) @@ -1267,6 +1277,8 @@ class WorkspaceUserProfileEndpoint(BaseAPIView): "started", ], project_issue__assignees__in=[user_id], + project_issue__archived_at__isnull=True, + project_issue__is_draft=False, ), ) ) @@ -1317,6 +1329,11 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView): def get(self, request, slug, user_id): try: filters = issue_filters(request.query_params, "GET") + + # Custom ordering for priority and state + priority_order = ["urgent", "high", "medium", "low", "none"] + state_order = ["backlog", "unstarted", "started", "completed", "cancelled"] + order_by_param = request.GET.get("order_by", "-created_at") issue_queryset = ( Issue.issue_objects.filter( diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py index 733defe69..6d33dfc4f 100644 --- a/apiserver/plane/bgtasks/issue_activites_task.py +++ b/apiserver/plane/bgtasks/issue_activites_task.py @@ -121,36 +121,20 @@ def track_priority( epoch ): if current_instance.get("priority") != requested_data.get("priority"): - if requested_data.get("priority") == None: - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value=current_instance.get("priority"), - new_value=None, - field="priority", - project=project, - workspace=project.workspace, - comment=f"updated the priority to None", - epoch=epoch, - ) - ) - else: - issue_activities.append( - IssueActivity( - issue_id=issue_id, - actor=actor, - verb="updated", - old_value=current_instance.get("priority"), - new_value=requested_data.get("priority"), - field="priority", - project=project, - workspace=project.workspace, - comment=f"updated the priority to {requested_data.get('priority')}", - epoch=epoch, - ) + issue_activities.append( + IssueActivity( + issue_id=issue_id, + actor=actor, + verb="updated", + old_value=current_instance.get("priority"), + new_value=requested_data.get("priority"), + field="priority", + project=project, + workspace=project.workspace, + comment=f"updated the priority to {requested_data.get('priority')}", + epoch=epoch, ) + ) # Track chnages in state of the issue diff --git a/apiserver/plane/bgtasks/issue_automation_task.py b/apiserver/plane/bgtasks/issue_automation_task.py index f7b06c625..a1b42073f 100644 --- a/apiserver/plane/bgtasks/issue_automation_task.py +++ b/apiserver/plane/bgtasks/issue_automation_task.py @@ -77,7 +77,7 @@ def archive_old_issues(): project_id=project_id, current_instance=None, subscriber=False, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) for issue in updated_issues ] @@ -149,7 +149,7 @@ def close_old_issues(): project_id=project_id, current_instance=None, subscriber=False, - epoch = int(timezone.now().timestamp()) + epoch=int(timezone.now().timestamp()) ) for issue in updated_issues ] diff --git a/apiserver/plane/db/migrations/0047_auto_20230921_0758.py b/apiserver/plane/db/migrations/0047_auto_20230921_0758.py new file mode 100644 index 000000000..4344963cd --- /dev/null +++ b/apiserver/plane/db/migrations/0047_auto_20230921_0758.py @@ -0,0 +1,27 @@ +# Generated by Django 4.2.3 on 2023-09-21 07:58 + + +from django.db import migrations + + +def update_priority_history(apps, schema_editor): + IssueActivity = apps.get_model("db", "IssueActivity") + updated_issue_activity = [] + for obj in IssueActivity.objects.all(): + if obj.field == "priority": + obj.new_value = obj.new_value or "none" + obj.old_value = obj.old_value or "none" + updated_issue_activity.append(obj) + IssueActivity.objects.bulk_update( + updated_issue_activity, ["new_value", "old_value"], batch_size=100 + ) + + +class Migration(migrations.Migration): + dependencies = [ + ("db", "0046_auto_20230919_1421"), + ] + + operations = [ + migrations.RunPython(update_priority_history), + ] diff --git a/apiserver/plane/utils/analytics_plot.py b/apiserver/plane/utils/analytics_plot.py index 60e751459..bffbb4c2a 100644 --- a/apiserver/plane/utils/analytics_plot.py +++ b/apiserver/plane/utils/analytics_plot.py @@ -74,10 +74,10 @@ def build_graph_plot(queryset, x_axis, y_axis, segment=None): sorted_data = grouped_data if temp_axis == "priority": - order = ["low", "medium", "high", "urgent", "None"] + order = ["low", "medium", "high", "urgent", "none"] sorted_data = {key: grouped_data[key] for key in order if key in grouped_data} else: - sorted_data = dict(sorted(grouped_data.items(), key=lambda x: (x[0] == "None", x[0]))) + sorted_data = dict(sorted(grouped_data.items(), key=lambda x: (x[0] == "none", x[0]))) return sorted_data diff --git a/apiserver/plane/utils/issue_filters.py b/apiserver/plane/utils/issue_filters.py index 226d909cd..3a869113c 100644 --- a/apiserver/plane/utils/issue_filters.py +++ b/apiserver/plane/utils/issue_filters.py @@ -40,9 +40,6 @@ def filter_priority(params, filter, method): priorities = params.get("priority").split(",") if len(priorities) and "" not in priorities: filter["priority__in"] = priorities - else: - if params.get("priority", None) and len(params.get("priority")): - filter["priority__in"] = params.get("priority") return filter diff --git a/web/components/analytics/scope-and-demand/scope.tsx b/web/components/analytics/scope-and-demand/scope.tsx index b01354b93..9231947bd 100644 --- a/web/components/analytics/scope-and-demand/scope.tsx +++ b/web/components/analytics/scope-and-demand/scope.tsx @@ -15,17 +15,19 @@ export const AnalyticsScope: React.FC = ({ defaultAnalytics }) => (
Pending issues
- {defaultAnalytics.pending_issue_user.length > 0 ? ( + {defaultAnalytics.pending_issue_user && defaultAnalytics.pending_issue_user.length > 0 ? ( `#f97316`} - customYAxisTickValues={defaultAnalytics.pending_issue_user.map((d) => d.count)} + customYAxisTickValues={defaultAnalytics.pending_issue_user.map((d) => + d.count > 0 ? d.count : 50 + )} tooltip={(datum) => { const assignee = defaultAnalytics.pending_issue_user.find( - (a) => a.assignees__display_name === `${datum.indexValue}` + (a) => a.assignees__id === `${datum.indexValue}` ); return ( @@ -39,10 +41,9 @@ export const AnalyticsScope: React.FC = ({ defaultAnalytics }) => ( }} axisBottom={{ renderTick: (datum) => { - const avatar = - defaultAnalytics.pending_issue_user[datum.tickIndex]?.assignees__avatar ?? ""; + const assignee = defaultAnalytics.pending_issue_user[datum.tickIndex] ?? ""; - if (avatar && avatar !== "") + if (assignee && assignee?.assignees__avatar && assignee?.assignees__avatar !== "") return ( = ({ defaultAnalytics }) => ( y={10} width={16} height={16} - xlinkHref={avatar} + xlinkHref={assignee?.assignees__avatar} style={{ clipPath: "circle(50%)" }} /> @@ -60,7 +61,7 @@ export const AnalyticsScope: React.FC = ({ defaultAnalytics }) => ( - {datum.value ? `${datum.value}`.toUpperCase()[0] : "?"} + {datum.value ? `${assignee.assignees__display_name}`.toUpperCase()[0] : "?"} ); diff --git a/web/components/core/views/board-view/index.ts b/web/components/core/views/board-view/index.ts index 6e5cdf8bf..a5a6ee497 100644 --- a/web/components/core/views/board-view/index.ts +++ b/web/components/core/views/board-view/index.ts @@ -2,3 +2,4 @@ export * from "./all-boards"; export * from "./board-header"; export * from "./single-board"; export * from "./single-issue"; +export * from "./inline-create-issue-form"; diff --git a/web/components/core/views/board-view/inline-create-issue-form.tsx b/web/components/core/views/board-view/inline-create-issue-form.tsx new file mode 100644 index 000000000..f4810164d --- /dev/null +++ b/web/components/core/views/board-view/inline-create-issue-form.tsx @@ -0,0 +1,62 @@ +import { useEffect } from "react"; + +// react hook form +import { useFormContext } from "react-hook-form"; + +// components +import { InlineCreateIssueFormWrapper } from "components/core"; + +// hooks +import useProjectDetails from "hooks/use-project-details"; + +// types +import { IIssue } from "types"; + +type Props = { + isOpen: boolean; + handleClose: () => void; + onSuccess?: (data: IIssue) => Promise | void; + prePopulatedData?: Partial; +}; + +const InlineInput = () => { + const { projectDetails } = useProjectDetails(); + + const { register, setFocus } = useFormContext(); + + useEffect(() => { + setFocus("name"); + }, [setFocus]); + + return ( +
+

+ {projectDetails?.identifier ?? "..."} +

+ +
+ ); +}; + +export const BoardInlineCreateIssueForm: React.FC = (props) => ( + <> + + + + {props.isOpen && ( +

+ Press {"'"}Enter{"'"} to add another issue +

+ )} + +); diff --git a/web/components/core/views/board-view/single-board.tsx b/web/components/core/views/board-view/single-board.tsx index 1981e1f7c..4226c3091 100644 --- a/web/components/core/views/board-view/single-board.tsx +++ b/web/components/core/views/board-view/single-board.tsx @@ -6,7 +6,7 @@ import { useRouter } from "next/router"; import StrictModeDroppable from "components/dnd/StrictModeDroppable"; import { Draggable } from "react-beautiful-dnd"; // components -import { BoardHeader, SingleBoardIssue } from "components/core"; +import { BoardHeader, SingleBoardIssue, BoardInlineCreateIssueForm } from "components/core"; // ui import { CustomMenu } from "components/ui"; // icons @@ -34,26 +34,30 @@ type Props = { viewProps: IIssueViewProps; }; -export const SingleBoard: React.FC = ({ - addIssueToGroup, - currentState, - groupTitle, - disableUserActions, - disableAddIssueOption = false, - dragDisabled, - handleIssueAction, - handleDraftIssueAction, - handleTrashBox, - openIssuesListModal, - handleMyIssueOpen, - removeIssue, - user, - userAuth, - viewProps, -}) => { +export const SingleBoard: React.FC = (props) => { + const { + addIssueToGroup, + currentState, + groupTitle, + disableUserActions, + disableAddIssueOption = false, + dragDisabled, + handleIssueAction, + handleDraftIssueAction, + handleTrashBox, + openIssuesListModal, + handleMyIssueOpen, + removeIssue, + user, + userAuth, + viewProps, + } = props; + // collapse/expand const [isCollapsed, setIsCollapsed] = useState(true); + const [isInlineCreateIssueFormOpen, setIsInlineCreateIssueFormOpen] = useState(false); + const { displayFilters, groupedIssues } = viewProps; const router = useRouter(); @@ -67,6 +71,24 @@ export const SingleBoard: React.FC = ({ const isNotAllowed = userAuth.isGuest || userAuth.isViewer || disableUserActions; + const onCreateClick = () => { + setIsInlineCreateIssueFormOpen(true); + + const boardListElement = document.getElementById(`board-list-${groupTitle}`); + + // timeout is needed because the animation + // takes time to complete & we can scroll only after that + const timeoutId = setTimeout(() => { + if (boardListElement) + boardListElement.scrollBy({ + top: boardListElement.scrollHeight, + left: 0, + behavior: "smooth", + }); + clearTimeout(timeoutId); + }, 10); + }; + return (
= ({ )}
= ({ type={type} index={index} issue={issue} + projectId={issue.project_detail.id} groupTitle={groupTitle} editIssue={() => handleIssueAction(issue, "edit")} makeIssueCopy={() => handleIssueAction(issue, "copy")} @@ -169,6 +193,19 @@ export const SingleBoard: React.FC = ({ > <>{provided.placeholder} + + setIsInlineCreateIssueFormOpen(false)} + prePopulatedData={{ + ...(cycleId && { cycle: cycleId.toString() }), + ...(moduleId && { module: moduleId.toString() }), + [displayFilters?.group_by! === "labels" + ? "labels_list" + : displayFilters?.group_by!]: + displayFilters?.group_by === "labels" ? [groupTitle] : groupTitle, + }} + />
{displayFilters?.group_by !== "created_by" && (
@@ -177,7 +214,7 @@ export const SingleBoard: React.FC = ({ @@ -224,7 +230,9 @@ export const SingleList: React.FC = ({ position="right" noBorder > - Create new + setIsCreateIssueFormOpen(true)}> + Create new + {openIssuesListModal && ( Add an existing issue @@ -250,6 +258,7 @@ export const SingleList: React.FC = ({ key={issue.id} type={type} issue={issue} + projectId={issue.project_detail.id} groupTitle={groupTitle} index={index} editIssue={() => handleIssueAction(issue, "edit")} @@ -284,6 +293,29 @@ export const SingleList: React.FC = ({ ) : (
Loading...
)} + + setIsCreateIssueFormOpen(false)} + prePopulatedData={{ + ...(cycleId && { cycle: cycleId.toString() }), + ...(moduleId && { module: moduleId.toString() }), + [displayFilters?.group_by!]: groupTitle, + }} + /> + + {!isCreateIssueFormOpen && ( +
+ +
+ )}
diff --git a/web/components/core/views/spreadsheet-view/single-issue.tsx b/web/components/core/views/spreadsheet-view/single-issue.tsx index 7a309f728..32cb4ba77 100644 --- a/web/components/core/views/spreadsheet-view/single-issue.tsx +++ b/web/components/core/views/spreadsheet-view/single-issue.tsx @@ -49,6 +49,7 @@ import { renderLongDetailDateFormat } from "helpers/date-time.helper"; type Props = { issue: IIssue; + projectId: string; index: number; expanded: boolean; handleToggleExpand: (issueId: string) => void; @@ -64,6 +65,7 @@ type Props = { export const SingleSpreadsheetIssue: React.FC = ({ issue, + projectId, index, expanded, handleToggleExpand, @@ -80,7 +82,7 @@ export const SingleSpreadsheetIssue: React.FC = ({ const router = useRouter(); - const { workspaceSlug, projectId, cycleId, moduleId, viewId } = router.query; + const { workspaceSlug, cycleId, moduleId, viewId } = router.query; const { params } = useSpreadsheetIssuesView(); @@ -96,7 +98,7 @@ export const SingleSpreadsheetIssue: React.FC = ({ ? MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), params) : viewId ? VIEW_ISSUES(viewId.toString(), params) - : PROJECT_ISSUES_LIST_WITH_PARAMS(projectId.toString(), params); + : PROJECT_ISSUES_LIST_WITH_PARAMS(projectId, params); if (issue.parent) mutate( @@ -136,13 +138,7 @@ export const SingleSpreadsheetIssue: React.FC = ({ ); issuesService - .patchIssue( - workspaceSlug as string, - projectId as string, - issue.id as string, - formData, - user - ) + .patchIssue(workspaceSlug as string, projectId, issue.id as string, formData, user) .then(() => { if (issue.parent) { mutate(SUB_ISSUES(issue.parent as string)); @@ -368,6 +364,7 @@ export const SingleSpreadsheetIssue: React.FC = ({
= ({
= ({
= ({
= ({ userAuth, }) => { const [expandedIssues, setExpandedIssues] = useState([]); + const [isInlineCreateIssueFormOpen, setIsInlineCreateIssueFormOpen] = useState(false); const router = useRouter(); const { workspaceSlug, projectId, cycleId, moduleId } = router.query; @@ -88,53 +89,59 @@ export const SpreadsheetView: React.FC = ({ userAuth={userAuth} /> ))} + + setIsInlineCreateIssueFormOpen(false)} + prePopulatedData={{ + ...(cycleId && { cycle: cycleId.toString() }), + ...(moduleId && { module: moduleId.toString() }), + }} + /> +
- {type === "issue" ? ( - - ) : ( - !disableUserActions && ( - - - Add Issue - - } - position="left" - optionsClassName="left-5 !w-36" - noBorder - > - { - const e = new KeyboardEvent("keydown", { key: "c" }); - document.dispatchEvent(e); - }} + {!isInlineCreateIssueFormOpen && ( + <> + {type === "issue" ? ( + + ) : ( + !disableUserActions && ( + + + Add Issue + + } + position="left" + optionsClassName="left-5 !w-36" + noBorder + > + setIsInlineCreateIssueFormOpen(true)}> + Create new + + {openIssuesListModal && ( + + Add an existing issue + + )} + + ) + )} + )}
diff --git a/web/components/gantt-chart/sidebar.tsx b/web/components/gantt-chart/sidebar.tsx index 92e7a603d..0d90ffdd0 100644 --- a/web/components/gantt-chart/sidebar.tsx +++ b/web/components/gantt-chart/sidebar.tsx @@ -1,3 +1,6 @@ +import { useState } from "react"; +// next +import { useRouter } from "next/router"; // react-beautiful-dnd import { DragDropContext, Draggable, DropResult } from "react-beautiful-dnd"; import StrictModeDroppable from "components/dnd/StrictModeDroppable"; @@ -7,6 +10,9 @@ import { useChart } from "./hooks"; import { Loader } from "components/ui"; // icons import { EllipsisVerticalIcon } from "@heroicons/react/24/outline"; +import { PlusIcon } from "lucide-react"; +// components +import { GanttInlineCreateIssueForm } from "components/core/views/gantt-chart-view/inline-create-issue-form"; // types import { IBlockUpdateData, IGanttBlock } from "./types"; @@ -18,15 +24,16 @@ type Props = { enableReorder: boolean; }; -export const GanttSidebar: React.FC = ({ - title, - blockUpdateHandler, - blocks, - SidebarBlockRender, - enableReorder, -}) => { +export const GanttSidebar: React.FC = (props) => { + const { title, blockUpdateHandler, blocks, SidebarBlockRender, enableReorder } = props; + + const router = useRouter(); + const { cycleId, moduleId } = router.query; + const { activeBlock, dispatch } = useChart(); + const [isCreateIssueFormOpen, setIsCreateIssueFormOpen] = useState(false); + // update the active block on hover const updateActiveBlock = (block: IGanttBlock | null) => { dispatch({ @@ -148,6 +155,28 @@ export const GanttSidebar: React.FC = ({ )} {droppableProvided.placeholder} + + setIsCreateIssueFormOpen(false)} + prePopulatedData={{ + start_date: new Date(Date.now()).toISOString().split("T")[0], + target_date: new Date(Date.now() + 86400000).toISOString().split("T")[0], + ...(cycleId && { cycle: cycleId.toString() }), + ...(moduleId && { module: moduleId.toString() }), + }} + /> + + {!isCreateIssueFormOpen && ( + + )}
)} diff --git a/web/components/project/label-select.tsx b/web/components/project/label-select.tsx index b4cc6da06..c155dea14 100644 --- a/web/components/project/label-select.tsx +++ b/web/components/project/label-select.tsx @@ -24,6 +24,7 @@ import { PROJECT_ISSUE_LABELS } from "constants/fetch-keys"; type Props = { value: string[]; + projectId: string; onChange: (data: any) => void; labelsDetails: any[]; className?: string; @@ -37,6 +38,7 @@ type Props = { export const LabelSelect: React.FC = ({ value, + projectId, onChange, labelsDetails, className = "", @@ -54,15 +56,15 @@ export const LabelSelect: React.FC = ({ const [labelModal, setLabelModal] = useState(false); const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug } = router.query; const dropdownBtn = useRef(null); const dropdownOptions = useRef(null); const { data: issueLabels } = useSWR( - projectId && fetchStates ? PROJECT_ISSUE_LABELS(projectId.toString()) : null, + projectId && fetchStates ? PROJECT_ISSUE_LABELS(projectId) : null, workspaceSlug && projectId && fetchStates - ? () => issuesService.getIssueLabels(workspaceSlug as string, projectId as string) + ? () => issuesService.getIssueLabels(workspaceSlug as string, projectId) : null ); @@ -150,7 +152,7 @@ export const LabelSelect: React.FC = ({ setLabelModal(false)} - projectId={projectId.toString()} + projectId={projectId} user={user} /> )} diff --git a/web/components/project/members-select.tsx b/web/components/project/members-select.tsx index 4ffad72b9..57523df8d 100644 --- a/web/components/project/members-select.tsx +++ b/web/components/project/members-select.tsx @@ -18,6 +18,7 @@ import { IUser } from "types"; type Props = { value: string | string[]; + projectId: string; onChange: (data: any) => void; membersDetails: IUser[]; renderWorkspaceMembers?: boolean; @@ -30,6 +31,7 @@ type Props = { export const MembersSelect: React.FC = ({ value, + projectId, onChange, membersDetails, renderWorkspaceMembers = false, @@ -44,14 +46,14 @@ export const MembersSelect: React.FC = ({ const [fetchStates, setFetchStates] = useState(false); const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug } = router.query; const dropdownBtn = useRef(null); const dropdownOptions = useRef(null); const { members } = useProjectMembers( workspaceSlug?.toString(), - projectId?.toString(), + projectId, fetchStates && !renderWorkspaceMembers ); diff --git a/web/components/project/single-project-card.tsx b/web/components/project/single-project-card.tsx index 547211c53..eb88e7381 100644 --- a/web/components/project/single-project-card.tsx +++ b/web/components/project/single-project-card.tsx @@ -149,7 +149,7 @@ export const SingleProjectCard: React.FC = ({ ) : ( - Member + Joined )} {project.is_favorite && ( diff --git a/web/components/states/state-select.tsx b/web/components/states/state-select.tsx index ed37e97b5..9f6b40d04 100644 --- a/web/components/states/state-select.tsx +++ b/web/components/states/state-select.tsx @@ -25,6 +25,7 @@ import { getStatesList } from "helpers/state.helper"; type Props = { value: IState; onChange: (data: any, states: IState[] | undefined) => void; + projectId: string; className?: string; buttonClassName?: string; optionsClassName?: string; @@ -35,6 +36,7 @@ type Props = { export const StateSelect: React.FC = ({ value, onChange, + projectId, className = "", buttonClassName = "", optionsClassName = "", @@ -50,12 +52,12 @@ export const StateSelect: React.FC = ({ const [fetchStates, setFetchStates] = useState(false); const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug } = router.query; const { data: stateGroups } = useSWR( - workspaceSlug && projectId && fetchStates ? STATES_LIST(projectId as string) : null, + workspaceSlug && projectId && fetchStates ? STATES_LIST(projectId) : null, workspaceSlug && projectId && fetchStates - ? () => stateService.getStates(workspaceSlug as string, projectId as string) + ? () => stateService.getStates(workspaceSlug as string, projectId) : null ); diff --git a/web/components/ui/toggle-switch.tsx b/web/components/ui/toggle-switch.tsx index e52ff26c9..d6c512ad7 100644 --- a/web/components/ui/toggle-switch.tsx +++ b/web/components/ui/toggle-switch.tsx @@ -35,7 +35,7 @@ export const ToggleSwitch: React.FC = (props) => { : size === "md" ? "translate-x-4" : "translate-x-5") + " bg-white" - : "translate-x-1 bg-custom-background-90" + : "translate-x-0.5 bg-custom-background-90" }`} /> diff --git a/web/helpers/string.helper.ts b/web/helpers/string.helper.ts index 0fc84fda1..d1e0e98b7 100644 --- a/web/helpers/string.helper.ts +++ b/web/helpers/string.helper.ts @@ -1,3 +1,10 @@ +import { + CYCLE_ISSUES_WITH_PARAMS, + MODULE_ISSUES_WITH_PARAMS, + PROJECT_ISSUES_LIST_WITH_PARAMS, + VIEW_ISSUES, +} from "constants/fetch-keys"; + export const addSpaceIfCamelCase = (str: string) => str.replace(/([a-z])([A-Z])/g, "$1 $2"); export const replaceUnderscoreIfSnakeCase = (str: string) => str.replace(/_/g, " "); @@ -122,3 +129,65 @@ export const objToQueryParams = (obj: any) => { return params.toString(); }; + +export const getFetchKeysForIssueMutation = (options: { + cycleId?: string | string[]; + moduleId?: string | string[]; + viewId?: string | string[]; + projectId: string; + calendarParams: any; + spreadsheetParams: any; + viewGanttParams: any; + ganttParams: any; +}) => { + const { + cycleId, + moduleId, + viewId, + projectId, + calendarParams, + spreadsheetParams, + viewGanttParams, + ganttParams, + } = options; + + const calendarFetchKey = cycleId + ? { calendarFetchKey: CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), calendarParams) } + : moduleId + ? { calendarFetchKey: MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), calendarParams) } + : viewId + ? { calendarFetchKey: VIEW_ISSUES(viewId.toString(), calendarParams) } + : { + calendarFetchKey: PROJECT_ISSUES_LIST_WITH_PARAMS( + projectId?.toString() ?? "", + calendarParams + ), + }; + + const spreadsheetFetchKey = cycleId + ? { spreadsheetFetchKey: CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), spreadsheetParams) } + : moduleId + ? { spreadsheetFetchKey: MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), spreadsheetParams) } + : viewId + ? { spreadsheetFetchKey: VIEW_ISSUES(viewId.toString(), spreadsheetParams) } + : { + spreadsheetFetchKey: PROJECT_ISSUES_LIST_WITH_PARAMS( + projectId?.toString() ?? "", + spreadsheetParams + ), + }; + + const ganttFetchKey = cycleId + ? { ganttFetchKey: CYCLE_ISSUES_WITH_PARAMS(cycleId.toString(), ganttParams) } + : moduleId + ? { ganttFetchKey: MODULE_ISSUES_WITH_PARAMS(moduleId.toString(), ganttParams) } + : viewId + ? { ganttFetchKey: VIEW_ISSUES(viewId.toString(), viewGanttParams) } + : { ganttFetchKey: PROJECT_ISSUES_LIST_WITH_PARAMS(projectId?.toString() ?? "", ganttParams) }; + + return { + ...calendarFetchKey, + ...spreadsheetFetchKey, + ...ganttFetchKey, + }; +}; diff --git a/web/hooks/gantt-chart/issue-view.tsx b/web/hooks/gantt-chart/issue-view.tsx index 8b24a566c..c2f6972fa 100644 --- a/web/hooks/gantt-chart/issue-view.tsx +++ b/web/hooks/gantt-chart/issue-view.tsx @@ -36,6 +36,7 @@ const useGanttChartIssues = (workspaceSlug: string | undefined, projectId: strin return { ganttIssues, mutateGanttIssues, + params, }; }; diff --git a/web/hooks/use-keypress.tsx b/web/hooks/use-keypress.tsx new file mode 100644 index 000000000..d04cd1445 --- /dev/null +++ b/web/hooks/use-keypress.tsx @@ -0,0 +1,19 @@ +import { useEffect } from "react"; + +const useKeypress = (key: string, callback: () => void) => { + useEffect(() => { + const handleKeydown = (event: KeyboardEvent) => { + if (event.key === key) { + callback(); + } + }; + + document.addEventListener("keydown", handleKeydown); + + return () => { + document.removeEventListener("keydown", handleKeydown); + }; + }); +}; + +export default useKeypress; diff --git a/web/pages/[workspaceSlug]/index.tsx b/web/pages/[workspaceSlug]/index.tsx index df1a69865..79e9571b7 100644 --- a/web/pages/[workspaceSlug]/index.tsx +++ b/web/pages/[workspaceSlug]/index.tsx @@ -127,9 +127,21 @@ const WorkspacePage: NextPage = () => { />
)} - {projects ? ( - projects.length > 0 ? ( -
+
+
+

+ Good {greeting}, {user?.first_name} {user?.last_name} +

+
+
{greeting === "morning" ? "🌤️" : greeting === "afternoon" ? "🌥️" : "🌙️"}
+
+ {DAYS[today.getDay()]}, {renderShortDate(today)} {render12HourFormatTime(today)} +
+
+
+ + {projects ? ( + projects.length > 0 ? (
@@ -143,17 +155,8 @@ const WorkspacePage: NextPage = () => { />
-
- ) : ( -
-

- Good {greeting}, {user?.first_name} {user?.last_name} -

-
- {greeting === "morning" ? "🌤️" : greeting === "afternoon" ? "🌥️" : "🌙️"} - {DAYS[today.getDay()]}, {renderShortDate(today)} {render12HourFormatTime(today)} -
-
+ ) : ( +
Create a project

@@ -174,9 +177,9 @@ const WorkspacePage: NextPage = () => { Empty Dashboard

-
- ) - ) : null} + ) + ) : null} +
); }; diff --git a/web/pages/[workspaceSlug]/me/profile/activity.tsx b/web/pages/[workspaceSlug]/me/profile/activity.tsx index ee527829b..d8588390e 100644 --- a/web/pages/[workspaceSlug]/me/profile/activity.tsx +++ b/web/pages/[workspaceSlug]/me/profile/activity.tsx @@ -46,7 +46,7 @@ const ProfileActivity = () => { {userActivity ? (
-

Acitivity

+

Activity

    diff --git a/web/pages/[workspaceSlug]/me/profile/preferences.tsx b/web/pages/[workspaceSlug]/me/profile/preferences.tsx index b1b16a3d4..eb6a3c821 100644 --- a/web/pages/[workspaceSlug]/me/profile/preferences.tsx +++ b/web/pages/[workspaceSlug]/me/profile/preferences.tsx @@ -66,7 +66,7 @@ const ProfilePreferences = observer(() => {
    -

    Acitivity

    +

    Preferences

    diff --git a/web/pages/[workspaceSlug]/settings/billing.tsx b/web/pages/[workspaceSlug]/settings/billing.tsx index 898b716bf..eeb95a5a3 100644 --- a/web/pages/[workspaceSlug]/settings/billing.tsx +++ b/web/pages/[workspaceSlug]/settings/billing.tsx @@ -50,7 +50,7 @@ const BillingSettings: NextPage = () => {
    -

    Billing & Plan

    +

    Billing & Plans