diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 46a6b6937..8d72ac5db 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -32,7 +32,6 @@ from plane.api.serializers import ( LabelSerializer, ) from plane.app.permissions import ( - WorkspaceEntityPermission, ProjectEntityPermission, ProjectLitePermission, ProjectMemberPermission, diff --git a/apiserver/plane/app/views/cycle/issue.py b/apiserver/plane/app/views/cycle/issue.py index 2a5505dd0..9a029eb25 100644 --- a/apiserver/plane/app/views/cycle/issue.py +++ b/apiserver/plane/app/views/cycle/issue.py @@ -38,7 +38,7 @@ from plane.db.models import ( ) from plane.bgtasks.issue_activites_task import issue_activity from plane.utils.issue_filters import issue_filters - +from plane.utils.user_timezone_converter import user_timezone_converter class CycleIssueViewSet(WebhookMixin, BaseViewSet): serializer_class = CycleIssueSerializer @@ -191,6 +191,11 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet): "is_draft", "archived_at", ) + datetime_fields = ["created_at", "updated_at"] + issues = user_timezone_converter( + issues, datetime_fields, request.user.user_timezone + ) + return Response(issues, status=status.HTTP_200_OK) def create(self, request, slug, project_id, cycle_id): diff --git a/apiserver/plane/app/views/issue/archive.py b/apiserver/plane/app/views/issue/archive.py index d9274ae4f..af019a7ec 100644 --- a/apiserver/plane/app/views/issue/archive.py +++ b/apiserver/plane/app/views/issue/archive.py @@ -47,7 +47,7 @@ from plane.db.models import ( ) from plane.bgtasks.issue_activites_task import issue_activity from plane.utils.issue_filters import issue_filters - +from plane.utils.user_timezone_converter import user_timezone_converter class IssueArchiveViewSet(BaseViewSet): permission_classes = [ @@ -239,6 +239,11 @@ class IssueArchiveViewSet(BaseViewSet): "is_draft", "archived_at", ) + datetime_fields = ["created_at", "updated_at"] + issues = user_timezone_converter( + issue_queryset, datetime_fields, request.user.user_timezone + ) + return Response(issues, status=status.HTTP_200_OK) def retrieve(self, request, slug, project_id, pk=None): diff --git a/apiserver/plane/app/views/issue/base.py b/apiserver/plane/app/views/issue/base.py index 23df58540..7a0e5d9b1 100644 --- a/apiserver/plane/app/views/issue/base.py +++ b/apiserver/plane/app/views/issue/base.py @@ -50,6 +50,7 @@ from plane.db.models import ( Project, ) from plane.utils.issue_filters import issue_filters +from plane.utils.user_timezone_converter import user_timezone_converter # Module imports from .. import BaseAPIView, BaseViewSet, WebhookMixin @@ -241,6 +242,10 @@ class IssueListEndpoint(BaseAPIView): "is_draft", "archived_at", ) + datetime_fields = ["created_at", "updated_at"] + issues = user_timezone_converter( + issues, datetime_fields, request.user.user_timezone + ) return Response(issues, status=status.HTTP_200_OK) @@ -440,6 +445,10 @@ class IssueViewSet(WebhookMixin, BaseViewSet): "is_draft", "archived_at", ) + datetime_fields = ["created_at", "updated_at"] + issues = user_timezone_converter( + issue_queryset, datetime_fields, request.user.user_timezone + ) return Response(issues, status=status.HTTP_200_OK) def create(self, request, slug, project_id): @@ -503,6 +512,10 @@ class IssueViewSet(WebhookMixin, BaseViewSet): ) .first() ) + datetime_fields = ["created_at", "updated_at"] + issue = user_timezone_converter( + issue, datetime_fields, request.user.user_timezone + ) return Response(issue, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) diff --git a/apiserver/plane/app/views/issue/draft.py b/apiserver/plane/app/views/issue/draft.py index 077d7dcaf..fe75c61f1 100644 --- a/apiserver/plane/app/views/issue/draft.py +++ b/apiserver/plane/app/views/issue/draft.py @@ -45,6 +45,7 @@ from plane.db.models import ( Project, ) from plane.utils.issue_filters import issue_filters +from plane.utils.user_timezone_converter import user_timezone_converter # Module imports from .. import BaseViewSet @@ -229,6 +230,10 @@ class IssueDraftViewSet(BaseViewSet): "is_draft", "archived_at", ) + datetime_fields = ["created_at", "updated_at"] + issues = user_timezone_converter( + issue_queryset, datetime_fields, request.user.user_timezone + ) return Response(issues, status=status.HTTP_200_OK) def create(self, request, slug, project_id): diff --git a/apiserver/plane/app/views/issue/sub_issue.py b/apiserver/plane/app/views/issue/sub_issue.py index da479e0e9..2ee4574eb 100644 --- a/apiserver/plane/app/views/issue/sub_issue.py +++ b/apiserver/plane/app/views/issue/sub_issue.py @@ -31,6 +31,7 @@ from plane.db.models import ( IssueAttachment, ) from plane.bgtasks.issue_activites_task import issue_activity +from plane.utils.user_timezone_converter import user_timezone_converter from collections import defaultdict @@ -132,6 +133,10 @@ class SubIssuesEndpoint(BaseAPIView): "is_draft", "archived_at", ) + datetime_fields = ["created_at", "updated_at"] + sub_issues = user_timezone_converter( + sub_issues, datetime_fields, request.user.user_timezone + ) return Response( { "sub_issues": sub_issues, diff --git a/apiserver/plane/app/views/module/archive.py b/apiserver/plane/app/views/module/archive.py index 9c0b6cca3..8a5345ff4 100644 --- a/apiserver/plane/app/views/module/archive.py +++ b/apiserver/plane/app/views/module/archive.py @@ -32,6 +32,8 @@ from plane.db.models import ( ModuleLink, ) from plane.utils.analytics_plot import burndown_plot +from plane.utils.user_timezone_converter import user_timezone_converter + # Module imports from .. import BaseAPIView @@ -199,6 +201,10 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView): "updated_at", "archived_at", ) + datetime_fields = ["created_at", "updated_at"] + modules = user_timezone_converter( + modules, datetime_fields, request.user.user_timezone + ) return Response(modules, status=status.HTTP_200_OK) else: queryset = ( diff --git a/apiserver/plane/app/views/module/base.py b/apiserver/plane/app/views/module/base.py index 4cd52b3b1..59f26a036 100644 --- a/apiserver/plane/app/views/module/base.py +++ b/apiserver/plane/app/views/module/base.py @@ -48,6 +48,8 @@ from plane.db.models import ( Project, ) from plane.utils.analytics_plot import burndown_plot +from plane.utils.user_timezone_converter import user_timezone_converter + # Module imports from .. import BaseAPIView, BaseViewSet, WebhookMixin @@ -236,6 +238,10 @@ class ModuleViewSet(WebhookMixin, BaseViewSet): "updated_at", ) ).first() + datetime_fields = ["created_at", "updated_at"] + module = user_timezone_converter( + module, datetime_fields, request.user.user_timezone + ) return Response(module, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @@ -277,6 +283,10 @@ class ModuleViewSet(WebhookMixin, BaseViewSet): "created_at", "updated_at", ) + datetime_fields = ["created_at", "updated_at"] + modules = user_timezone_converter( + modules, datetime_fields, request.user.user_timezone + ) return Response(modules, status=status.HTTP_200_OK) def retrieve(self, request, slug, project_id, pk): @@ -454,6 +464,10 @@ class ModuleViewSet(WebhookMixin, BaseViewSet): "created_at", "updated_at", ).first() + datetime_fields = ["created_at", "updated_at"] + module = user_timezone_converter( + module, datetime_fields, request.user.user_timezone + ) return Response(module, status=status.HTTP_200_OK) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) diff --git a/apiserver/plane/app/views/module/issue.py b/apiserver/plane/app/views/module/issue.py index d26433340..e0fcb2d3c 100644 --- a/apiserver/plane/app/views/module/issue.py +++ b/apiserver/plane/app/views/module/issue.py @@ -31,7 +31,7 @@ from plane.db.models import ( ) from plane.bgtasks.issue_activites_task import issue_activity from plane.utils.issue_filters import issue_filters - +from plane.utils.user_timezone_converter import user_timezone_converter class ModuleIssueViewSet(WebhookMixin, BaseViewSet): serializer_class = ModuleIssueSerializer @@ -150,6 +150,11 @@ class ModuleIssueViewSet(WebhookMixin, BaseViewSet): "is_draft", "archived_at", ) + datetime_fields = ["created_at", "updated_at"] + issues = user_timezone_converter( + issues, datetime_fields, request.user.user_timezone + ) + return Response(issues, status=status.HTTP_200_OK) # create multiple issues inside a module diff --git a/apiserver/plane/app/views/view/base.py b/apiserver/plane/app/views/view/base.py index 35772ccf3..7736e465c 100644 --- a/apiserver/plane/app/views/view/base.py +++ b/apiserver/plane/app/views/view/base.py @@ -42,7 +42,7 @@ from plane.db.models import ( IssueAttachment, ) from plane.utils.issue_filters import issue_filters - +from plane.utils.user_timezone_converter import user_timezone_converter class GlobalViewViewSet(BaseViewSet): serializer_class = IssueViewSerializer @@ -255,6 +255,10 @@ class GlobalViewIssuesViewSet(BaseViewSet): "is_draft", "archived_at", ) + datetime_fields = ["created_at", "updated_at"] + issues = user_timezone_converter( + issues, datetime_fields, request.user.user_timezone + ) return Response(issues, status=status.HTTP_200_OK) diff --git a/apiserver/plane/utils/user_timezone_converter.py b/apiserver/plane/utils/user_timezone_converter.py new file mode 100644 index 000000000..579b96c26 --- /dev/null +++ b/apiserver/plane/utils/user_timezone_converter.py @@ -0,0 +1,25 @@ +import pytz + +def user_timezone_converter(queryset, datetime_fields, user_timezone): + # Create a timezone object for the user's timezone + user_tz = pytz.timezone(user_timezone) + + # Check if queryset is a dictionary (single item) or a list of dictionaries + if isinstance(queryset, dict): + queryset_values = [queryset] + else: + queryset_values = list(queryset.values()) + + # Iterate over the dictionaries in the list + for item in queryset_values: + # Iterate over the datetime fields + for field in datetime_fields: + # Convert the datetime field to the user's timezone + if item[field]: + item[field] = item[field].astimezone(user_tz) + + # If queryset was a single item, return a single item + if isinstance(queryset, dict): + return queryset_values[0] + else: + return queryset_values