[WEB-780]feat: project cycle module archive (#3990)

* dev: project cycle module archive

* fix: update filter logic updated
This commit is contained in:
Bavisetti Narayan 2024-03-20 13:59:31 +05:30 committed by GitHub
parent cb4cfa1dd5
commit 2f883e4939
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
40 changed files with 854 additions and 22 deletions

View File

@ -4,6 +4,7 @@ from plane.api.views.cycle import (
CycleAPIEndpoint, CycleAPIEndpoint,
CycleIssueAPIEndpoint, CycleIssueAPIEndpoint,
TransferCycleIssueAPIEndpoint, TransferCycleIssueAPIEndpoint,
CycleArchiveUnarchiveAPIEndpoint,
) )
urlpatterns = [ urlpatterns = [
@ -32,4 +33,14 @@ urlpatterns = [
TransferCycleIssueAPIEndpoint.as_view(), TransferCycleIssueAPIEndpoint.as_view(),
name="transfer-issues", name="transfer-issues",
), ),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:pk>/archive/",
CycleArchiveUnarchiveAPIEndpoint.as_view(),
name="cycle-archive-unarchive",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-cycles/",
CycleArchiveUnarchiveAPIEndpoint.as_view(),
name="cycle-archive-unarchive",
),
] ]

View File

@ -1,6 +1,10 @@
from django.urls import path from django.urls import path
from plane.api.views import ModuleAPIEndpoint, ModuleIssueAPIEndpoint from plane.api.views import (
ModuleAPIEndpoint,
ModuleIssueAPIEndpoint,
ModuleArchiveUnarchiveAPIEndpoint,
)
urlpatterns = [ urlpatterns = [
path( path(
@ -23,4 +27,14 @@ urlpatterns = [
ModuleIssueAPIEndpoint.as_view(), ModuleIssueAPIEndpoint.as_view(),
name="module-issues", name="module-issues",
), ),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:pk>/archive/",
ModuleArchiveUnarchiveAPIEndpoint.as_view(),
name="module-archive-unarchive",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-modules/",
ModuleArchiveUnarchiveAPIEndpoint.as_view(),
name="module-archive-unarchive",
),
] ]

View File

@ -1,6 +1,9 @@
from django.urls import path from django.urls import path
from plane.api.views import ProjectAPIEndpoint from plane.api.views import (
ProjectAPIEndpoint,
ProjectArchiveUnarchiveAPIEndpoint,
)
urlpatterns = [ urlpatterns = [
path( path(
@ -13,4 +16,9 @@ urlpatterns = [
ProjectAPIEndpoint.as_view(), ProjectAPIEndpoint.as_view(),
name="project", name="project",
), ),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/archive/",
ProjectArchiveUnarchiveAPIEndpoint.as_view(),
name="project-archive-unarchive",
),
] ]

View File

@ -1,4 +1,4 @@
from .project import ProjectAPIEndpoint from .project import ProjectAPIEndpoint, ProjectArchiveUnarchiveAPIEndpoint
from .state import StateAPIEndpoint from .state import StateAPIEndpoint
@ -14,8 +14,13 @@ from .cycle import (
CycleAPIEndpoint, CycleAPIEndpoint,
CycleIssueAPIEndpoint, CycleIssueAPIEndpoint,
TransferCycleIssueAPIEndpoint, TransferCycleIssueAPIEndpoint,
CycleArchiveUnarchiveAPIEndpoint,
) )
from .module import ModuleAPIEndpoint, ModuleIssueAPIEndpoint from .module import (
ModuleAPIEndpoint,
ModuleIssueAPIEndpoint,
ModuleArchiveUnarchiveAPIEndpoint,
)
from .inbox import InboxIssueAPIEndpoint from .inbox import InboxIssueAPIEndpoint

View File

@ -140,7 +140,9 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
def get(self, request, slug, project_id, pk=None): def get(self, request, slug, project_id, pk=None):
if pk: if pk:
queryset = self.get_queryset().get(pk=pk) queryset = (
self.get_queryset().filter(archived_at__isnull=True).get(pk=pk)
)
data = CycleSerializer( data = CycleSerializer(
queryset, queryset,
fields=self.fields, fields=self.fields,
@ -150,7 +152,9 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
data, data,
status=status.HTTP_200_OK, status=status.HTTP_200_OK,
) )
queryset = self.get_queryset() queryset = (
self.get_queryset().filter(archived_at__isnull=True)
)
cycle_view = request.GET.get("cycle_view", "all") cycle_view = request.GET.get("cycle_view", "all")
# Current Cycle # Current Cycle
@ -291,6 +295,11 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
cycle = Cycle.objects.get( cycle = Cycle.objects.get(
workspace__slug=slug, project_id=project_id, pk=pk workspace__slug=slug, project_id=project_id, pk=pk
) )
if cycle.archived_at:
return Response(
{"error": "Archived cycle cannot be edited"},
status=status.HTTP_400_BAD_REQUEST,
)
request_data = request.data request_data = request.data
@ -368,6 +377,139 @@ class CycleAPIEndpoint(WebhookMixin, BaseAPIView):
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
class CycleArchiveUnarchiveAPIEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
def get_queryset(self):
return (
Cycle.objects.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(
project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True,
)
.filter(archived_at__isnull=False)
.select_related("project")
.select_related("workspace")
.select_related("owned_by")
.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",
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",
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",
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",
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",
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",
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",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.order_by(self.kwargs.get("order_by", "-created_at"))
.distinct()
)
def list(self, request, slug, project_id):
return self.paginate(
request=request,
queryset=(self.get_queryset()),
on_results=lambda cycles: CycleSerializer(
cycles,
many=True,
fields=self.fields,
expand=self.expand,
).data,
)
def post(self, request, slug, project_id, pk):
cycle = Cycle.objects.get(
pk=pk, project_id=project_id, workspace__slug=slug
)
cycle.archived_at = timezone.now()
cycle.save()
return Response(status=status.HTTP_204_NO_CONTENT)
def delete(self, request, slug, project_id, pk):
cycle = Cycle.objects.get(
pk=pk, project_id=project_id, workspace__slug=slug
)
cycle.archived_at = None
cycle.save()
return Response(status=status.HTTP_204_NO_CONTENT)
class CycleIssueAPIEndpoint(WebhookMixin, BaseAPIView): class CycleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
""" """
This viewset automatically provides `list`, `create`, This viewset automatically provides `list`, `create`,

View File

@ -357,6 +357,7 @@ class LabelAPIEndpoint(BaseAPIView):
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
) )
.filter(project__archived_at__isnull=True)
.select_related("project") .select_related("project")
.select_related("workspace") .select_related("workspace")
.select_related("parent") .select_related("parent")
@ -489,6 +490,7 @@ class IssueLinkAPIEndpoint(BaseAPIView):
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
) )
.filter(project__archived_at__isnull=True)
.order_by(self.kwargs.get("order_by", "-created_at")) .order_by(self.kwargs.get("order_by", "-created_at"))
.distinct() .distinct()
) )
@ -618,6 +620,7 @@ class IssueCommentAPIEndpoint(WebhookMixin, BaseAPIView):
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
) )
.filter(project__archived_at__isnull=True)
.select_related("workspace", "project", "issue", "actor") .select_related("workspace", "project", "issue", "actor")
.annotate( .annotate(
is_member=Exists( is_member=Exists(
@ -793,6 +796,7 @@ class IssueActivityAPIEndpoint(BaseAPIView):
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
) )
.filter(project__archived_at__isnull=True)
.select_related("actor", "workspace", "issue", "project") .select_related("actor", "workspace", "issue", "project")
).order_by(request.GET.get("order_by", "created_at")) ).order_by(request.GET.get("order_by", "created_at"))

View File

@ -165,6 +165,11 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):
module = Module.objects.get( module = Module.objects.get(
pk=pk, project_id=project_id, workspace__slug=slug pk=pk, project_id=project_id, workspace__slug=slug
) )
if module.archived_at:
return Response(
{"error": "Archived module cannot be edited"},
status=status.HTTP_400_BAD_REQUEST,
)
serializer = ModuleSerializer( serializer = ModuleSerializer(
module, module,
data=request.data, data=request.data,
@ -197,7 +202,9 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):
def get(self, request, slug, project_id, pk=None): def get(self, request, slug, project_id, pk=None):
if pk: if pk:
queryset = self.get_queryset().get(pk=pk) queryset = (
self.get_queryset().filter(archived_at__isnull=True).get(pk=pk)
)
data = ModuleSerializer( data = ModuleSerializer(
queryset, queryset,
fields=self.fields, fields=self.fields,
@ -209,7 +216,7 @@ class ModuleAPIEndpoint(WebhookMixin, BaseAPIView):
) )
return self.paginate( return self.paginate(
request=request, request=request,
queryset=(self.get_queryset()), queryset=(self.get_queryset().filter(archived_at__isnull=True)),
on_results=lambda modules: ModuleSerializer( on_results=lambda modules: ModuleSerializer(
modules, modules,
many=True, many=True,
@ -279,6 +286,7 @@ class ModuleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
) )
.filter(project__archived_at__isnull=True)
.select_related("project") .select_related("project")
.select_related("workspace") .select_related("workspace")
.select_related("module") .select_related("module")
@ -446,3 +454,117 @@ class ModuleIssueAPIEndpoint(WebhookMixin, BaseAPIView):
epoch=int(timezone.now().timestamp()), epoch=int(timezone.now().timestamp()),
) )
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
class ModuleArchiveUnarchiveAPIEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
def get_queryset(self):
return (
Module.objects.filter(project_id=self.kwargs.get("project_id"))
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(archived_at__isnull=False)
.select_related("project")
.select_related("workspace")
.select_related("lead")
.prefetch_related("members")
.prefetch_related(
Prefetch(
"link_module",
queryset=ModuleLink.objects.select_related(
"module", "created_by"
),
)
)
.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",
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",
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",
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",
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",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
),
)
)
.order_by(self.kwargs.get("order_by", "-created_at"))
)
def list(self, request, slug, project_id):
return self.paginate(
request=request,
queryset=(self.get_queryset()),
on_results=lambda modules: ModuleSerializer(
modules,
many=True,
fields=self.fields,
expand=self.expand,
).data,
)
def post(self, request, slug, project_id, pk):
module = Module.objects.get(
pk=pk, project_id=project_id, workspace__slug=slug
)
module.archived_at = timezone.now()
module.save()
return Response(status=status.HTTP_204_NO_CONTENT)
def delete(self, request, slug, project_id, pk):
module = Module.objects.get(
pk=pk, project_id=project_id, workspace__slug=slug
)
module.archived_at = None
module.save()
return Response(status=status.HTTP_204_NO_CONTENT)

View File

@ -1,4 +1,5 @@
# Django imports # Django imports
from django.utils import timezone
from django.db import IntegrityError from django.db import IntegrityError
from django.db.models import Exists, OuterRef, Q, F, Func, Subquery, Prefetch from django.db.models import Exists, OuterRef, Q, F, Func, Subquery, Prefetch
@ -39,7 +40,10 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
return ( return (
Project.objects.filter(workspace__slug=self.kwargs.get("slug")) Project.objects.filter(workspace__slug=self.kwargs.get("slug"))
.filter( .filter(
Q(project_projectmember__member=self.request.user) Q(
project_projectmember__member=self.request.user,
project_projectmember__is_active=True,
)
| Q(network=2) | Q(network=2)
) )
.select_related( .select_related(
@ -260,6 +264,12 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
workspace = Workspace.objects.get(slug=slug) workspace = Workspace.objects.get(slug=slug)
project = Project.objects.get(pk=project_id) project = Project.objects.get(pk=project_id)
if project.archived_at:
return Response(
{"error": "Archived project cannot be updated"},
status=status.HTTP_400_BAD_REQUEST,
)
serializer = ProjectSerializer( serializer = ProjectSerializer(
project, project,
data={**request.data}, data={**request.data},
@ -316,3 +326,22 @@ class ProjectAPIEndpoint(WebhookMixin, BaseAPIView):
project = Project.objects.get(pk=project_id, workspace__slug=slug) project = Project.objects.get(pk=project_id, workspace__slug=slug)
project.delete() project.delete()
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
class ProjectArchiveUnarchiveAPIEndpoint(BaseAPIView):
permission_classes = [
ProjectBasePermission,
]
def post(self, request, slug, project_id):
project = Project.objects.get(pk=project_id, workspace__slug=slug)
project.archived_at = timezone.now()
project.save()
return Response(status=status.HTTP_204_NO_CONTENT)
def delete(self, request, slug, project_id):
project = Project.objects.get(pk=project_id, workspace__slug=slug)
project.archived_at = None
project.save()
return Response(status=status.HTTP_204_NO_CONTENT)

View File

@ -28,6 +28,7 @@ class StateAPIEndpoint(BaseAPIView):
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
) )
.filter(project__archived_at__isnull=True)
.filter(~Q(name="Triage")) .filter(~Q(name="Triage"))
.select_related("project") .select_related("project")
.select_related("workspace") .select_related("workspace")

View File

@ -31,6 +31,7 @@ class CycleWriteSerializer(BaseSerializer):
"workspace", "workspace",
"project", "project",
"owned_by", "owned_by",
"archived_at",
] ]

View File

@ -39,6 +39,7 @@ class ModuleWriteSerializer(BaseSerializer):
"updated_by", "updated_by",
"created_at", "created_at",
"updated_at", "updated_at",
"archived_at",
] ]
def to_representation(self, instance): def to_representation(self, instance):

View File

@ -8,6 +8,7 @@ from plane.app.views import (
CycleFavoriteViewSet, CycleFavoriteViewSet,
TransferCycleIssueEndpoint, TransferCycleIssueEndpoint,
CycleUserPropertiesEndpoint, CycleUserPropertiesEndpoint,
CycleArchiveUnarchiveEndpoint,
) )
@ -90,4 +91,14 @@ urlpatterns = [
CycleUserPropertiesEndpoint.as_view(), CycleUserPropertiesEndpoint.as_view(),
name="cycle-user-filters", name="cycle-user-filters",
), ),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/cycles/<uuid:cycle_id>/archive/",
CycleArchiveUnarchiveEndpoint.as_view(),
name="cycle-archive-unarchive",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-cycles/",
CycleArchiveUnarchiveEndpoint.as_view(),
name="cycle-archive-unarchive",
),
] ]

View File

@ -7,6 +7,7 @@ from plane.app.views import (
ModuleLinkViewSet, ModuleLinkViewSet,
ModuleFavoriteViewSet, ModuleFavoriteViewSet,
ModuleUserPropertiesEndpoint, ModuleUserPropertiesEndpoint,
ModuleArchiveUnarchiveEndpoint,
) )
@ -110,4 +111,14 @@ urlpatterns = [
ModuleUserPropertiesEndpoint.as_view(), ModuleUserPropertiesEndpoint.as_view(),
name="cycle-user-filters", name="cycle-user-filters",
), ),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/modules/<uuid:module_id>/archive/",
ModuleArchiveUnarchiveEndpoint.as_view(),
name="module-archive-unarchive",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/archived-modules/",
ModuleArchiveUnarchiveEndpoint.as_view(),
name="module-archive-unarchive",
),
] ]

View File

@ -14,6 +14,7 @@ from plane.app.views import (
ProjectPublicCoverImagesEndpoint, ProjectPublicCoverImagesEndpoint,
ProjectDeployBoardViewSet, ProjectDeployBoardViewSet,
UserProjectRolesEndpoint, UserProjectRolesEndpoint,
ProjectArchiveUnarchiveEndpoint,
) )
@ -175,4 +176,9 @@ urlpatterns = [
), ),
name="project-deploy-board", name="project-deploy-board",
), ),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/archive/",
ProjectArchiveUnarchiveEndpoint.as_view(),
name="project-archive-unarchive",
),
] ]

View File

@ -5,6 +5,7 @@ from .project.base import (
ProjectFavoritesViewSet, ProjectFavoritesViewSet,
ProjectPublicCoverImagesEndpoint, ProjectPublicCoverImagesEndpoint,
ProjectDeployBoardViewSet, ProjectDeployBoardViewSet,
ProjectArchiveUnarchiveEndpoint,
) )
from .project.invite import ( from .project.invite import (
@ -90,6 +91,7 @@ from .cycle.base import (
CycleDateCheckEndpoint, CycleDateCheckEndpoint,
CycleFavoriteViewSet, CycleFavoriteViewSet,
TransferCycleIssueEndpoint, TransferCycleIssueEndpoint,
CycleArchiveUnarchiveEndpoint,
CycleUserPropertiesEndpoint, CycleUserPropertiesEndpoint,
) )
from .cycle.issue import ( from .cycle.issue import (
@ -168,6 +170,7 @@ from .module.base import (
ModuleViewSet, ModuleViewSet,
ModuleLinkViewSet, ModuleLinkViewSet,
ModuleFavoriteViewSet, ModuleFavoriteViewSet,
ModuleArchiveUnarchiveEndpoint,
ModuleUserPropertiesEndpoint, ModuleUserPropertiesEndpoint,
) )

View File

@ -80,6 +80,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
) )
.filter(project__archived_at__isnull=True)
.select_related("project", "workspace", "owned_by") .select_related("project", "workspace", "owned_by")
.prefetch_related( .prefetch_related(
Prefetch( Prefetch(
@ -184,13 +185,17 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
) )
def list(self, request, slug, project_id): def list(self, request, slug, project_id):
queryset = self.get_queryset().annotate( queryset = (
total_issues=Count( self.get_queryset()
"issue_cycle", .filter(archived_at__isnull=True)
filter=Q( .annotate(
issue_cycle__issue__archived_at__isnull=True, total_issues=Count(
issue_cycle__issue__is_draft=False, "issue_cycle",
), filter=Q(
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
) )
) )
cycle_view = request.GET.get("cycle_view", "all") cycle_view = request.GET.get("cycle_view", "all")
@ -420,6 +425,11 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
workspace__slug=slug, project_id=project_id, pk=pk workspace__slug=slug, project_id=project_id, pk=pk
) )
cycle = queryset.first() cycle = queryset.first()
if cycle.archived_at:
return Response(
{"error": "Archived cycle cannot be updated"},
status=status.HTTP_400_BAD_REQUEST,
)
request_data = request.data request_data = request.data
if ( if (
@ -478,6 +488,7 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
def retrieve(self, request, slug, project_id, pk): def retrieve(self, request, slug, project_id, pk):
queryset = ( queryset = (
self.get_queryset() self.get_queryset()
.filter(archived_at__isnull=True)
.filter(pk=pk) .filter(pk=pk)
.annotate( .annotate(
total_issues=Count( total_issues=Count(
@ -682,6 +693,192 @@ class CycleViewSet(WebhookMixin, BaseViewSet):
return Response(status=status.HTTP_204_NO_CONTENT) return Response(status=status.HTTP_204_NO_CONTENT)
class CycleArchiveUnarchiveEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
def get_queryset(self):
favorite_subquery = CycleFavorite.objects.filter(
user=self.request.user,
cycle_id=OuterRef("pk"),
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
return self.filter_queryset(
super()
.get_queryset()
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(project_id=self.kwargs.get("project_id"))
.filter(archived_at__isnull=False)
.filter(
project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True,
)
.filter(project__archived_at__isnull=True)
.select_related("project", "workspace", "owned_by")
.prefetch_related(
Prefetch(
"issue_cycle__issue__assignees",
queryset=User.objects.only(
"avatar", "first_name", "id"
).distinct(),
)
)
.prefetch_related(
Prefetch(
"issue_cycle__issue__labels",
queryset=Label.objects.only(
"name", "color", "id"
).distinct(),
)
)
.annotate(is_favorite=Exists(favorite_subquery))
.annotate(
completed_issues=Count(
"issue_cycle__issue__state__group",
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",
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",
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",
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",
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.annotate(
status=Case(
When(
Q(start_date__lte=timezone.now())
& Q(end_date__gte=timezone.now()),
then=Value("CURRENT"),
),
When(
start_date__gt=timezone.now(), then=Value("UPCOMING")
),
When(end_date__lt=timezone.now(), then=Value("COMPLETED")),
When(
Q(start_date__isnull=True) & Q(end_date__isnull=True),
then=Value("DRAFT"),
),
default=Value("DRAFT"),
output_field=CharField(),
)
)
.annotate(
assignee_ids=Coalesce(
ArrayAgg(
"issue_cycle__issue__assignees__id",
distinct=True,
filter=~Q(
issue_cycle__issue__assignees__id__isnull=True
),
),
Value([], output_field=ArrayField(UUIDField())),
)
)
.order_by("-is_favorite", "name")
.distinct()
)
def list(self, request, slug, project_id):
queryset = (
self.get_queryset()
.annotate(
total_issues=Count(
"issue_cycle",
filter=Q(
issue_cycle__issue__archived_at__isnull=True,
issue_cycle__issue__is_draft=False,
),
)
)
.values(
# necessary fields
"id",
"workspace_id",
"project_id",
# model fields
"name",
"description",
"start_date",
"end_date",
"owned_by_id",
"view_props",
"sort_order",
"external_source",
"external_id",
"progress_snapshot",
# meta fields
"total_issues",
"is_favorite",
"cancelled_issues",
"completed_issues",
"started_issues",
"unstarted_issues",
"backlog_issues",
"assignee_ids",
"status",
)
).order_by("-is_favorite", "-created_at")
return Response(queryset, status=status.HTTP_200_OK)
def post(self, request, slug, project_id, cycle_id):
cycle = Cycle.objects.get(
pk=cycle_id, project_id=project_id, workspace__slug=slug
)
cycle.archived_at = timezone.now()
cycle.save()
return Response(status=status.HTTP_204_NO_CONTENT)
def delete(self, request, slug, project_id, cycle_id):
cycle = Cycle.objects.get(
pk=cycle_id, project_id=project_id, workspace__slug=slug
)
cycle.archived_at = None
cycle.save()
return Response(status=status.HTTP_204_NO_CONTENT)
class CycleDateCheckEndpoint(BaseAPIView): class CycleDateCheckEndpoint(BaseAPIView):
permission_classes = [ permission_classes = [
ProjectEntityPermission, ProjectEntityPermission,

View File

@ -74,6 +74,7 @@ class CycleIssueViewSet(WebhookMixin, BaseViewSet):
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
) )
.filter(project__archived_at__isnull=True)
.filter(cycle_id=self.kwargs.get("cycle_id")) .filter(cycle_id=self.kwargs.get("cycle_id"))
.select_related("project") .select_related("project")
.select_related("workspace") .select_related("workspace")

View File

@ -471,6 +471,7 @@ def dashboard_recent_activity(self, request, slug):
workspace__slug=slug, workspace__slug=slug,
project__project_projectmember__member=request.user, project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
actor=request.user, actor=request.user,
).select_related("actor", "workspace", "issue", "project")[:8] ).select_related("actor", "workspace", "issue", "project")[:8]
@ -486,6 +487,7 @@ def dashboard_recent_projects(self, request, slug):
workspace__slug=slug, workspace__slug=slug,
project__project_projectmember__member=request.user, project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
actor=request.user, actor=request.user,
) )
.values_list("project_id", flat=True) .values_list("project_id", flat=True)
@ -500,6 +502,7 @@ def dashboard_recent_projects(self, request, slug):
additional_projects = Project.objects.filter( additional_projects = Project.objects.filter(
project_projectmember__member=request.user, project_projectmember__member=request.user,
project_projectmember__is_active=True, project_projectmember__is_active=True,
archived_at__isnull=True,
workspace__slug=slug, workspace__slug=slug,
).exclude(id__in=unique_project_ids) ).exclude(id__in=unique_project_ids)
@ -522,6 +525,7 @@ def dashboard_recent_collaborators(self, request, slug):
actor=OuterRef("member"), actor=OuterRef("member"),
project__project_projectmember__member=request.user, project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
) )
.values("actor") .values("actor")
.annotate(num_activities=Count("pk")) .annotate(num_activities=Count("pk"))
@ -534,6 +538,7 @@ def dashboard_recent_collaborators(self, request, slug):
workspace__slug=slug, workspace__slug=slug,
project__project_projectmember__member=request.user, project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
) )
.annotate( .annotate(
num_activities=Coalesce( num_activities=Coalesce(

View File

@ -29,7 +29,10 @@ class ExportIssuesEndpoint(BaseAPIView):
if provider in ["csv", "xlsx", "json"]: if provider in ["csv", "xlsx", "json"]:
if not project_ids: if not project_ids:
project_ids = Project.objects.filter( project_ids = Project.objects.filter(
workspace__slug=slug workspace__slug=slug,
project_projectmember__member=request.user,
project_projectmember__is_active=True,
archived_at__isnull=True,
).values_list("id", flat=True) ).values_list("id", flat=True)
project_ids = [str(project_id) for project_id in project_ids] project_ids = [str(project_id) for project_id in project_ids]

View File

@ -44,6 +44,7 @@ class IssueActivityEndpoint(BaseAPIView):
~Q(field__in=["comment", "vote", "reaction", "draft"]), ~Q(field__in=["comment", "vote", "reaction", "draft"]),
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
workspace__slug=slug, workspace__slug=slug,
) )
.filter(**filters) .filter(**filters)
@ -54,6 +55,7 @@ class IssueActivityEndpoint(BaseAPIView):
.filter( .filter(
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
workspace__slug=slug, workspace__slug=slug,
) )
.filter(**filters) .filter(**filters)

View File

@ -48,6 +48,7 @@ class IssueCommentViewSet(WebhookMixin, BaseViewSet):
.filter( .filter(
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
) )
.select_related("project") .select_related("project")
.select_related("workspace") .select_related("workspace")
@ -163,6 +164,7 @@ class CommentReactionViewSet(BaseViewSet):
.filter( .filter(
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
) )
.order_by("-created_at") .order_by("-created_at")
.distinct() .distinct()

View File

@ -35,6 +35,7 @@ class IssueLinkViewSet(BaseViewSet):
.filter( .filter(
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
) )
.order_by("-created_at") .order_by("-created_at")
.distinct() .distinct()

View File

@ -34,6 +34,7 @@ class IssueReactionViewSet(BaseViewSet):
.filter( .filter(
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
) )
.order_by("-created_at") .order_by("-created_at")
.distinct() .distinct()

View File

@ -41,6 +41,7 @@ class IssueRelationViewSet(BaseViewSet):
.filter( .filter(
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
) )
.select_related("project") .select_related("project")
.select_related("workspace") .select_related("workspace")

View File

@ -54,6 +54,7 @@ class IssueSubscriberViewSet(BaseViewSet):
.filter( .filter(
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
) )
.order_by("-created_at") .order_by("-created_at")
.distinct() .distinct()

View File

@ -196,7 +196,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def list(self, request, slug, project_id): def list(self, request, slug, project_id):
queryset = self.get_queryset() queryset = self.get_queryset().filter(archived_at__isnull=True)
if self.fields: if self.fields:
modules = ModuleSerializer( modules = ModuleSerializer(
queryset, queryset,
@ -238,6 +238,7 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
def retrieve(self, request, slug, project_id, pk): def retrieve(self, request, slug, project_id, pk):
queryset = ( queryset = (
self.get_queryset() self.get_queryset()
.filter(archived_at__isnull=True)
.filter(pk=pk) .filter(pk=pk)
.annotate( .annotate(
total_issues=Issue.issue_objects.filter( total_issues=Issue.issue_objects.filter(
@ -374,14 +375,20 @@ class ModuleViewSet(WebhookMixin, BaseViewSet):
) )
def partial_update(self, request, slug, project_id, pk): def partial_update(self, request, slug, project_id, pk):
queryset = self.get_queryset().filter(pk=pk) module = self.get_queryset().filter(pk=pk)
if module.first().archived_at:
return Response(
{"error": "Archived module cannot be updated"},
status=status.HTTP_400_BAD_REQUEST,
)
serializer = ModuleWriteSerializer( serializer = ModuleWriteSerializer(
queryset.first(), data=request.data, partial=True module.first(), data=request.data, partial=True
) )
if serializer.is_valid(): if serializer.is_valid():
serializer.save() serializer.save()
module = queryset.values( module = module.values(
# Required fields # Required fields
"id", "id",
"workspace_id", "workspace_id",
@ -464,12 +471,167 @@ class ModuleLinkViewSet(BaseViewSet):
.filter( .filter(
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
) )
.order_by("-created_at") .order_by("-created_at")
.distinct() .distinct()
) )
class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
permission_classes = [
ProjectEntityPermission,
]
def get_queryset(self):
favorite_subquery = ModuleFavorite.objects.filter(
user=self.request.user,
module_id=OuterRef("pk"),
project_id=self.kwargs.get("project_id"),
workspace__slug=self.kwargs.get("slug"),
)
return (
super()
.get_queryset()
.filter(project_id=self.kwargs.get("project_id"))
.filter(workspace__slug=self.kwargs.get("slug"))
.filter(archived_at__isnull=False)
.annotate(is_favorite=Exists(favorite_subquery))
.select_related("project")
.select_related("workspace")
.select_related("lead")
.prefetch_related("members")
.prefetch_related(
Prefetch(
"link_module",
queryset=ModuleLink.objects.select_related(
"module", "created_by"
),
)
)
.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",
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",
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",
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",
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",
issue_module__issue__archived_at__isnull=True,
issue_module__issue__is_draft=False,
),
)
)
.annotate(
member_ids=Coalesce(
ArrayAgg(
"members__id",
distinct=True,
filter=~Q(members__id__isnull=True),
),
Value([], output_field=ArrayField(UUIDField())),
)
)
.order_by("-is_favorite", "-created_at")
)
def list(self, request, slug, project_id):
queryset = self.get_queryset()
modules = queryset.values( # Required fields
"id",
"workspace_id",
"project_id",
# Model fields
"name",
"description",
"description_text",
"description_html",
"start_date",
"target_date",
"status",
"lead_id",
"member_ids",
"view_props",
"sort_order",
"external_source",
"external_id",
# computed fields
"total_issues",
"is_favorite",
"cancelled_issues",
"completed_issues",
"started_issues",
"unstarted_issues",
"backlog_issues",
"created_at",
"updated_at",
)
return Response(modules, status=status.HTTP_200_OK)
def post(self, request, slug, project_id, module_id):
module = Module.objects.get(
pk=module_id, project_id=project_id, workspace__slug=slug
)
module.archived_at = timezone.now()
module.save()
return Response(status=status.HTTP_204_NO_CONTENT)
def delete(self, request, slug, project_id, module_id):
module = Module.objects.get(
pk=module_id, project_id=project_id, workspace__slug=slug
)
module.archived_at = None
module.save()
return Response(status=status.HTTP_204_NO_CONTENT)
class ModuleFavoriteViewSet(BaseViewSet): class ModuleFavoriteViewSet(BaseViewSet):
serializer_class = ModuleFavoriteSerializer serializer_class = ModuleFavoriteSerializer
model = ModuleFavorite model = ModuleFavorite

View File

@ -70,6 +70,7 @@ class PageViewSet(BaseViewSet):
.filter( .filter(
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
) )
.filter(parent__isnull=True) .filter(parent__isnull=True)
.filter(Q(owned_by=self.request.user) | Q(access=0)) .filter(Q(owned_by=self.request.user) | Q(access=0))

View File

@ -13,6 +13,7 @@ from django.db.models import (
Subquery, Subquery,
) )
from django.conf import settings from django.conf import settings
from django.utils import timezone
# Third Party imports # Third Party imports
from rest_framework.response import Response from rest_framework.response import Response
@ -179,6 +180,7 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
def retrieve(self, request, slug, pk): def retrieve(self, request, slug, pk):
project = ( project = (
self.get_queryset() self.get_queryset()
.filter(archived_at__isnull=True)
.filter(pk=pk) .filter(pk=pk)
.annotate( .annotate(
total_issues=Issue.issue_objects.filter( total_issues=Issue.issue_objects.filter(
@ -366,6 +368,12 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
project = Project.objects.get(pk=pk) project = Project.objects.get(pk=pk)
if project.archived_at:
return Response(
{"error": "Archived projects cannot be updated"},
status=status.HTTP_400_BAD_REQUEST,
)
serializer = ProjectSerializer( serializer = ProjectSerializer(
project, project,
data={**request.data}, data={**request.data},
@ -420,6 +428,24 @@ class ProjectViewSet(WebhookMixin, BaseViewSet):
) )
class ProjectArchiveUnarchiveEndpoint(BaseAPIView):
permission_classes = [
ProjectBasePermission,
]
def post(self, request, slug, project_id):
project = Project.objects.get(pk=project_id, workspace__slug=slug)
project.archived_at = timezone.now()
project.save()
return Response(status=status.HTTP_204_NO_CONTENT)
def delete(self, request, slug, project_id):
project = Project.objects.get(pk=project_id, workspace__slug=slug)
project.archived_at = None
project.save()
return Response(status=status.HTTP_204_NO_CONTENT)
class ProjectIdentifierEndpoint(BaseAPIView): class ProjectIdentifierEndpoint(BaseAPIView):
permission_classes = [ permission_classes = [
ProjectBasePermission, ProjectBasePermission,

View File

@ -50,6 +50,7 @@ class GlobalSearchEndpoint(BaseAPIView):
q, q,
project_projectmember__member=self.request.user, project_projectmember__member=self.request.user,
project_projectmember__is_active=True, project_projectmember__is_active=True,
archived_at__isnull=True,
workspace__slug=slug, workspace__slug=slug,
) )
.distinct() .distinct()
@ -72,6 +73,7 @@ class GlobalSearchEndpoint(BaseAPIView):
q, q,
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
workspace__slug=slug, workspace__slug=slug,
) )
@ -97,6 +99,7 @@ class GlobalSearchEndpoint(BaseAPIView):
q, q,
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
workspace__slug=slug, workspace__slug=slug,
) )
@ -121,6 +124,7 @@ class GlobalSearchEndpoint(BaseAPIView):
q, q,
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
workspace__slug=slug, workspace__slug=slug,
) )
@ -145,6 +149,7 @@ class GlobalSearchEndpoint(BaseAPIView):
q, q,
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
workspace__slug=slug, workspace__slug=slug,
) )
@ -169,6 +174,7 @@ class GlobalSearchEndpoint(BaseAPIView):
q, q,
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
workspace__slug=slug, workspace__slug=slug,
) )
@ -243,6 +249,7 @@ class IssueSearchEndpoint(BaseAPIView):
workspace__slug=slug, workspace__slug=slug,
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True
) )
if workspace_search == "false": if workspace_search == "false":

View File

@ -33,6 +33,7 @@ class StateViewSet(BaseViewSet):
.filter( .filter(
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
) )
.filter(~Q(name="Triage")) .filter(~Q(name="Triage"))
.select_related("project") .select_related("project")

View File

@ -282,6 +282,7 @@ class IssueViewViewSet(BaseViewSet):
.filter( .filter(
project__project_projectmember__member=self.request.user, project__project_projectmember__member=self.request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
) )
.select_related("project") .select_related("project")
.select_related("workspace") .select_related("workspace")

View File

@ -20,6 +20,7 @@ class WorkspaceLabelsEndpoint(BaseAPIView):
workspace__slug=slug, workspace__slug=slug,
project__project_projectmember__member=request.user, project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
) )
serializer = LabelSerializer(labels, many=True).data serializer = LabelSerializer(labels, many=True).data
return Response(serializer, status=status.HTTP_200_OK) return Response(serializer, status=status.HTTP_200_OK)

View File

@ -20,6 +20,7 @@ class WorkspaceStatesEndpoint(BaseAPIView):
workspace__slug=slug, workspace__slug=slug,
project__project_projectmember__member=request.user, project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
) )
serializer = StateSerializer(states, many=True).data serializer = StateSerializer(states, many=True).data
return Response(serializer, status=status.HTTP_200_OK) return Response(serializer, status=status.HTTP_200_OK)

View File

@ -124,7 +124,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView):
| Q(issue_subscribers__subscriber_id=user_id), | Q(issue_subscribers__subscriber_id=user_id),
workspace__slug=slug, workspace__slug=slug,
project__project_projectmember__member=request.user, project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True project__project_projectmember__is_active=True,
) )
.filter(**filters) .filter(**filters)
.select_related("workspace", "project", "state", "parent") .select_related("workspace", "project", "state", "parent")
@ -299,6 +299,7 @@ class WorkspaceUserProfileEndpoint(BaseAPIView):
workspace__slug=slug, workspace__slug=slug,
project_projectmember__member=request.user, project_projectmember__member=request.user,
project_projectmember__is_active=True, project_projectmember__is_active=True,
archived_at__isnull=True,
) )
.annotate( .annotate(
created_issues=Count( created_issues=Count(
@ -387,6 +388,7 @@ class WorkspaceUserActivityEndpoint(BaseAPIView):
workspace__slug=slug, workspace__slug=slug,
project__project_projectmember__member=request.user, project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
actor=user_id, actor=user_id,
).select_related("actor", "workspace", "issue", "project") ).select_related("actor", "workspace", "issue", "project")
@ -498,6 +500,7 @@ class WorkspaceUserProfileStatsEndpoint(BaseAPIView):
subscriber_id=user_id, subscriber_id=user_id,
project__project_projectmember__member=request.user, project__project_projectmember__member=request.user,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
) )
.filter(**filters) .filter(**filters)
.count() .count()

View File

@ -304,6 +304,7 @@ def issue_export_task(
project_id__in=project_ids, project_id__in=project_ids,
project__project_projectmember__member=exporter_instance.initiated_by_id, project__project_projectmember__member=exporter_instance.initiated_by_id,
project__project_projectmember__is_active=True, project__project_projectmember__is_active=True,
project__archived_at__isnull=True,
) )
.select_related( .select_related(
"project", "workspace", "state", "parent", "created_by" "project", "workspace", "state", "parent", "created_by"

View File

@ -0,0 +1,41 @@
# Generated by Django 4.2.7 on 2024-03-19 08:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('db', '0061_project_logo_props'),
]
operations = [
migrations.AddField(
model_name="cycle",
name="archived_at",
field=models.DateTimeField(null=True),
),
migrations.AddField(
model_name="module",
name="archived_at",
field=models.DateTimeField(null=True),
),
migrations.AddField(
model_name="project",
name="archived_at",
field=models.DateTimeField(null=True),
),
migrations.AlterField(
model_name="socialloginconnection",
name="medium",
field=models.CharField(
choices=[
("Google", "google"),
("Github", "github"),
("Jira", "jira"),
],
default=None,
max_length=20,
),
),
]

View File

@ -69,6 +69,7 @@ class Cycle(ProjectBaseModel):
external_source = models.CharField(max_length=255, null=True, blank=True) external_source = models.CharField(max_length=255, null=True, blank=True)
external_id = models.CharField(max_length=255, blank=True, null=True) external_id = models.CharField(max_length=255, blank=True, null=True)
progress_snapshot = models.JSONField(default=dict) progress_snapshot = models.JSONField(default=dict)
archived_at = models.DateTimeField(null=True)
class Meta: class Meta:
verbose_name = "Cycle" verbose_name = "Cycle"

View File

@ -91,6 +91,7 @@ class IssueManager(models.Manager):
| models.Q(issue_inbox__isnull=True) | models.Q(issue_inbox__isnull=True)
) )
.exclude(archived_at__isnull=False) .exclude(archived_at__isnull=False)
.exclude(project__archived_at__isnull=False)
.exclude(is_draft=True) .exclude(is_draft=True)
) )

View File

@ -92,6 +92,7 @@ class Module(ProjectBaseModel):
sort_order = models.FloatField(default=65535) sort_order = models.FloatField(default=65535)
external_source = models.CharField(max_length=255, null=True, blank=True) external_source = models.CharField(max_length=255, null=True, blank=True)
external_id = models.CharField(max_length=255, blank=True, null=True) external_id = models.CharField(max_length=255, blank=True, null=True)
archived_at = models.DateTimeField(null=True)
class Meta: class Meta:
unique_together = ["name", "project"] unique_together = ["name", "project"]

View File

@ -114,6 +114,7 @@ class Project(BaseModel):
null=True, null=True,
related_name="default_state", related_name="default_state",
) )
archived_at = models.DateTimeField(null=True)
def __str__(self): def __str__(self):
"""Return name of the project""" """Return name of the project"""