From 7bf4620bc191fabdbd10e176572decf0682e9cdc Mon Sep 17 00:00:00 2001 From: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Date: Tue, 22 Oct 2024 14:21:26 +0530 Subject: [PATCH] chore: soft deletion of cycle and module (#5884) * chore: soft deletion of cycle and module * chore: cycle module soft delete * chore: added the deletion task * chore: updated the env example * chore: cycle issue unique constraints * chore: udpated the Q operator --- apiserver/.env.example | 3 +- apiserver/plane/api/serializers/issue.py | 4 +- apiserver/plane/api/views/cycle.py | 2 +- apiserver/plane/api/views/inbox.py | 11 +++- apiserver/plane/api/views/issue.py | 2 +- apiserver/plane/app/serializers/draft.py | 10 +-- apiserver/plane/app/serializers/issue.py | 4 +- apiserver/plane/app/views/analytic/base.py | 5 +- apiserver/plane/app/views/cycle/base.py | 7 ++- apiserver/plane/app/views/cycle/issue.py | 2 +- apiserver/plane/app/views/dashboard/base.py | 38 ++++++----- apiserver/plane/app/views/inbox/base.py | 55 ++++++++++------ apiserver/plane/app/views/issue/archive.py | 2 +- apiserver/plane/app/views/issue/base.py | 63 ++++++++++++------- apiserver/plane/app/views/issue/relation.py | 13 ++-- apiserver/plane/app/views/issue/sub_issue.py | 21 ++++--- apiserver/plane/app/views/module/issue.py | 2 +- apiserver/plane/app/views/view/base.py | 27 +++++--- apiserver/plane/app/views/workspace/draft.py | 17 +++-- apiserver/plane/app/views/workspace/user.py | 2 +- .../plane/bgtasks/analytic_plot_export.py | 4 +- apiserver/plane/celery.py | 8 +-- ...anagers_alter_cycleissue_issue_and_more.py | 63 +++++++++++++++++++ apiserver/plane/db/models/cycle.py | 10 ++- apiserver/plane/db/models/draft.py | 10 ++- apiserver/plane/db/models/issue.py | 1 + apiserver/plane/space/serializer/issue.py | 4 +- apiserver/plane/space/utils/grouper.py | 12 +++- apiserver/plane/space/views/issue.py | 15 ++--- apiserver/plane/utils/grouper.py | 11 +++- apiserver/plane/utils/issue_filters.py | 30 ++++++--- 31 files changed, 317 insertions(+), 141 deletions(-) create mode 100644 apiserver/plane/db/migrations/0082_alter_issue_managers_alter_cycleissue_issue_and_more.py diff --git a/apiserver/.env.example b/apiserver/.env.example index 733e448d6..33ef5c4cd 100644 --- a/apiserver/.env.example +++ b/apiserver/.env.example @@ -57,5 +57,6 @@ ADMIN_BASE_URL= SPACE_BASE_URL= APP_BASE_URL= + # Hard delete files after days -HARD_DELETE_AFTER_DAYS=60 +HARD_DELETE_AFTER_DAYS=60 \ No newline at end of file diff --git a/apiserver/plane/api/serializers/issue.py b/apiserver/plane/api/serializers/issue.py index c4a131fd7..905157339 100644 --- a/apiserver/plane/api/serializers/issue.py +++ b/apiserver/plane/api/serializers/issue.py @@ -212,7 +212,7 @@ class IssueSerializer(BaseSerializer): updated_by_id = instance.updated_by_id if assignees is not None: - IssueAssignee.objects.filter(issue=instance).delete(soft=False) + IssueAssignee.objects.filter(issue=instance).delete() IssueAssignee.objects.bulk_create( [ IssueAssignee( @@ -229,7 +229,7 @@ class IssueSerializer(BaseSerializer): ) if labels is not None: - IssueLabel.objects.filter(issue=instance).delete(soft=False) + IssueLabel.objects.filter(issue=instance).delete() IssueLabel.objects.bulk_create( [ IssueLabel( diff --git a/apiserver/plane/api/views/cycle.py b/apiserver/plane/api/views/cycle.py index 882692dac..12e4a1b3c 100644 --- a/apiserver/plane/api/views/cycle.py +++ b/apiserver/plane/api/views/cycle.py @@ -404,7 +404,7 @@ class CycleAPIEndpoint(BaseAPIView): epoch=int(timezone.now().timestamp()), ) # Delete the cycle - cycle.delete(soft=False) + cycle.delete() # Delete the user favorite cycle UserFavorite.objects.filter( entity_type="cycle", diff --git a/apiserver/plane/api/views/inbox.py b/apiserver/plane/api/views/inbox.py index f7e18dd76..381e12aab 100644 --- a/apiserver/plane/api/views/inbox.py +++ b/apiserver/plane/api/views/inbox.py @@ -227,7 +227,10 @@ class InboxIssueAPIEndpoint(BaseAPIView): ArrayAgg( "labels__id", distinct=True, - filter=~Q(labels__id__isnull=True), + filter=Q( + ~Q(labels__id__isnull=True) + & Q(label_issue__deleted_at__isnull=True), + ), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -235,7 +238,11 @@ class InboxIssueAPIEndpoint(BaseAPIView): ArrayAgg( "assignees__id", distinct=True, - filter=~Q(assignees__id__isnull=True), + filter=Q( + ~Q(assignees__id__isnull=True) + & Q(assignees__member_project__is_active=True) + & Q(issue_assignee__deleted_at__isnull=True) + ), ), Value([], output_field=ArrayField(UUIDField())), ), diff --git a/apiserver/plane/api/views/issue.py b/apiserver/plane/api/views/issue.py index 27c7042b4..2590532ab 100644 --- a/apiserver/plane/api/views/issue.py +++ b/apiserver/plane/api/views/issue.py @@ -205,7 +205,7 @@ class IssueAPIEndpoint(BaseAPIView): .annotate( cycle_id=Case( When( - Q(issue_cycle__cycle__deleted_at__isnull=True), + Q(issue_cycle__deleted_at__isnull=True), then=F("issue_cycle__cycle_id"), ), default=None, diff --git a/apiserver/plane/app/serializers/draft.py b/apiserver/plane/app/serializers/draft.py index fca695c98..e07e416a7 100644 --- a/apiserver/plane/app/serializers/draft.py +++ b/apiserver/plane/app/serializers/draft.py @@ -169,9 +169,7 @@ class DraftIssueCreateSerializer(BaseSerializer): updated_by_id = instance.updated_by_id if assignees is not None: - DraftIssueAssignee.objects.filter(draft_issue=instance).delete( - soft=False - ) + DraftIssueAssignee.objects.filter(draft_issue=instance).delete() DraftIssueAssignee.objects.bulk_create( [ DraftIssueAssignee( @@ -188,9 +186,7 @@ class DraftIssueCreateSerializer(BaseSerializer): ) if labels is not None: - DraftIssueLabel.objects.filter(draft_issue=instance).delete( - soft=False - ) + DraftIssueLabel.objects.filter(draft_issue=instance).delete() DraftIssueLabel.objects.bulk_create( [ DraftIssueLabel( @@ -208,7 +204,7 @@ class DraftIssueCreateSerializer(BaseSerializer): if cycle_id != "not_provided": DraftIssueCycle.objects.filter(draft_issue=instance).delete() - if cycle_id is not None: + if cycle_id: DraftIssueCycle.objects.create( cycle_id=cycle_id, draft_issue=instance, diff --git a/apiserver/plane/app/serializers/issue.py b/apiserver/plane/app/serializers/issue.py index 22d9dc483..2323c248a 100644 --- a/apiserver/plane/app/serializers/issue.py +++ b/apiserver/plane/app/serializers/issue.py @@ -201,7 +201,7 @@ class IssueCreateSerializer(BaseSerializer): updated_by_id = instance.updated_by_id if assignees is not None: - IssueAssignee.objects.filter(issue=instance).delete(soft=False) + IssueAssignee.objects.filter(issue=instance).delete() IssueAssignee.objects.bulk_create( [ IssueAssignee( @@ -218,7 +218,7 @@ class IssueCreateSerializer(BaseSerializer): ) if labels is not None: - IssueLabel.objects.filter(issue=instance).delete(soft=False) + IssueLabel.objects.filter(issue=instance).delete() IssueLabel.objects.bulk_create( [ IssueLabel( diff --git a/apiserver/plane/app/views/analytic/base.py b/apiserver/plane/app/views/analytic/base.py index ddbde0269..47b4c1c70 100644 --- a/apiserver/plane/app/views/analytic/base.py +++ b/apiserver/plane/app/views/analytic/base.py @@ -110,7 +110,10 @@ class AnalyticsEndpoint(BaseAPIView): if x_axis in ["labels__id"] or segment in ["labels__id"]: label_details = ( Issue.objects.filter( - workspace__slug=slug, **filters, labels__id__isnull=False + workspace__slug=slug, + **filters, + labels__id__isnull=False + & Q(label_issue__deleted_at__isnull=True), ) .distinct("labels__id") .order_by("labels__id") diff --git a/apiserver/plane/app/views/cycle/base.py b/apiserver/plane/app/views/cycle/base.py index e021c4ba1..a61994766 100644 --- a/apiserver/plane/app/views/cycle/base.py +++ b/apiserver/plane/app/views/cycle/base.py @@ -144,6 +144,11 @@ class CycleViewSet(BaseViewSet): distinct=True, filter=~Q( issue_cycle__issue__assignees__id__isnull=True + ) + & ( + Q( + issue_cycle__issue__issue_assignee__deleted_at__isnull=True + ) ), ), Value([], output_field=ArrayField(UUIDField())), @@ -491,7 +496,7 @@ class CycleViewSet(BaseViewSet): origin=request.META.get("HTTP_ORIGIN"), ) # TODO: Soft delete the cycle break the onetoone relationship with cycle issue - cycle.delete(soft=False) + cycle.delete() # Delete the user favorite cycle UserFavorite.objects.filter( diff --git a/apiserver/plane/app/views/cycle/issue.py b/apiserver/plane/app/views/cycle/issue.py index a9a330599..c744897a1 100644 --- a/apiserver/plane/app/views/cycle/issue.py +++ b/apiserver/plane/app/views/cycle/issue.py @@ -105,7 +105,7 @@ class CycleIssueViewSet(BaseViewSet): .annotate( cycle_id=Case( When( - issue_cycle__cycle__deleted_at__isnull=True, + issue_cycle__deleted_at__isnull=True, then=F("issue_cycle__cycle_id"), ), default=None, diff --git a/apiserver/plane/app/views/dashboard/base.py b/apiserver/plane/app/views/dashboard/base.py index 1cb446abb..3bfa0fe56 100644 --- a/apiserver/plane/app/views/dashboard/base.py +++ b/apiserver/plane/app/views/dashboard/base.py @@ -218,9 +218,9 @@ def dashboard_assigned_issues(self, request, slug): ArrayAgg( "labels__id", distinct=True, - filter=( + filter=Q( ~Q(labels__id__isnull=True) - & Q(labels__deleted_at__isnull=True) + & Q(label_issue__deleted_at__isnull=True), ), ), Value([], output_field=ArrayField(UUIDField())), @@ -229,8 +229,11 @@ def dashboard_assigned_issues(self, request, slug): ArrayAgg( "assignees__id", distinct=True, - filter=~Q(assignees__id__isnull=True) - & Q(assignees__member_project__is_active=True), + filter=Q( + ~Q(assignees__id__isnull=True) + & Q(assignees__member_project__is_active=True) + & Q(issue_assignee__deleted_at__isnull=True) + ), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -238,9 +241,11 @@ def dashboard_assigned_issues(self, request, slug): ArrayAgg( "issue_module__module_id", distinct=True, - filter=~Q(issue_module__module_id__isnull=True) - & Q(issue_module__module__archived_at__isnull=True) - & Q(issue_module__module__deleted_at__isnull=True), + filter=Q( + ~Q(issue_module__module_id__isnull=True) + & Q(issue_module__module__archived_at__isnull=True) + & Q(issue_module__deleted_at__isnull=True) + ), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -387,9 +392,9 @@ def dashboard_created_issues(self, request, slug): ArrayAgg( "labels__id", distinct=True, - filter=( + filter=Q( ~Q(labels__id__isnull=True) - & Q(labels__deleted_at__isnull=True) + & Q(label_issue__deleted_at__isnull=True), ), ), Value([], output_field=ArrayField(UUIDField())), @@ -398,8 +403,11 @@ def dashboard_created_issues(self, request, slug): ArrayAgg( "assignees__id", distinct=True, - filter=~Q(assignees__id__isnull=True) - & Q(assignees__member_project__is_active=True), + filter=Q( + ~Q(assignees__id__isnull=True) + & Q(assignees__member_project__is_active=True) + & Q(issue_assignee__deleted_at__isnull=True) + ), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -407,9 +415,11 @@ def dashboard_created_issues(self, request, slug): ArrayAgg( "issue_module__module_id", distinct=True, - filter=~Q(issue_module__module_id__isnull=True) - & Q(issue_module__module__archived_at__isnull=True) - & Q(issue_module__module__deleted_at__isnull=True), + filter=Q( + ~Q(issue_module__module_id__isnull=True) + & Q(issue_module__module__archived_at__isnull=True) + & Q(issue_module__deleted_at__isnull=True) + ), ), Value([], output_field=ArrayField(UUIDField())), ), diff --git a/apiserver/plane/app/views/inbox/base.py b/apiserver/plane/app/views/inbox/base.py index dcae298d6..62da2b251 100644 --- a/apiserver/plane/app/views/inbox/base.py +++ b/apiserver/plane/app/views/inbox/base.py @@ -115,7 +115,7 @@ class InboxIssueViewSet(BaseViewSet): .annotate( cycle_id=Case( When( - issue_cycle__cycle__deleted_at__isnull=True, + issue_cycle__deleted_at__isnull=True, then=F("issue_cycle__cycle_id"), ), default=None, @@ -149,9 +149,9 @@ class InboxIssueViewSet(BaseViewSet): ArrayAgg( "labels__id", distinct=True, - filter=( + filter=Q( ~Q(labels__id__isnull=True) - & Q(labels__deleted_at__isnull=True) + & Q(label_issue__deleted_at__isnull=True), ), ), Value([], output_field=ArrayField(UUIDField())), @@ -160,8 +160,11 @@ class InboxIssueViewSet(BaseViewSet): ArrayAgg( "assignees__id", distinct=True, - filter=~Q(assignees__id__isnull=True) - & Q(assignees__member_project__is_active=True), + filter=Q( + ~Q(assignees__id__isnull=True) + & Q(assignees__member_project__is_active=True) + & Q(issue_assignee__deleted_at__isnull=True) + ), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -169,9 +172,11 @@ class InboxIssueViewSet(BaseViewSet): ArrayAgg( "issue_module__module_id", distinct=True, - filter=~Q(issue_module__module_id__isnull=True) - & Q(issue_module__module__archived_at__isnull=True) - & Q(issue_module__module__deleted_at__isnull=True), + filter=Q( + ~Q(issue_module__module_id__isnull=True) + & Q(issue_module__module__archived_at__isnull=True) + & Q(issue_module__deleted_at__isnull=True) + ), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -198,8 +203,8 @@ class InboxIssueViewSet(BaseViewSet): ArrayAgg( "issue__labels__id", distinct=True, - filter=~Q(issue__labels__id__isnull=True) - & Q(issue__labels__deleted_at__isnull=True), + filter=Q(~Q(issue__labels__id__isnull=True) + & Q(issue__label_issue__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ) @@ -311,9 +316,11 @@ class InboxIssueViewSet(BaseViewSet): ArrayAgg( "issue__labels__id", distinct=True, - filter=( + filter=Q( ~Q(issue__labels__id__isnull=True) - & Q(issue__labels__deleted_at__isnull=True) + & Q( + issue__label_issue__deleted_at__isnull=True + ) ), ), Value([], output_field=ArrayField(UUIDField())), @@ -323,7 +330,9 @@ class InboxIssueViewSet(BaseViewSet): "issue__assignees__id", distinct=True, filter=~Q(issue__assignees__id__isnull=True) - & Q(issue__assignees__member_project__is_active=True), + & Q( + issue__assignees__member_project__is_active=True + ), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -376,7 +385,8 @@ class InboxIssueViewSet(BaseViewSet): ArrayAgg( "labels__id", distinct=True, - filter=~Q(labels__id__isnull=True), + filter=Q(~Q(labels__id__isnull=True) + & Q(label_issue__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -384,7 +394,8 @@ class InboxIssueViewSet(BaseViewSet): ArrayAgg( "assignees__id", distinct=True, - filter=~Q(assignees__id__isnull=True), + filter=Q(~Q(assignees__id__isnull=True) + & Q(issue_assignee__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -507,7 +518,10 @@ class InboxIssueViewSet(BaseViewSet): ArrayAgg( "issue__labels__id", distinct=True, - filter=~Q(issue__labels__id__isnull=True), + filter=Q(~Q(issue__labels__id__isnull=True) + & Q( + issue__label_issue__deleted_at__isnull=True + )), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -515,7 +529,8 @@ class InboxIssueViewSet(BaseViewSet): ArrayAgg( "issue__assignees__id", distinct=True, - filter=~Q(issue__assignees__id__isnull=True), + filter=Q(~Q(issue__assignees__id__isnull=True) + & Q(issue__issue_assignee__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -560,7 +575,8 @@ class InboxIssueViewSet(BaseViewSet): ArrayAgg( "issue__labels__id", distinct=True, - filter=~Q(issue__labels__id__isnull=True), + filter=Q(~Q(issue__labels__id__isnull=True) + & Q(issue__label_issue__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -568,7 +584,8 @@ class InboxIssueViewSet(BaseViewSet): ArrayAgg( "issue__assignees__id", distinct=True, - filter=~Q(issue__assignees__id__isnull=True), + filter=Q(~Q(issue__assignees__id__isnull=True) + & Q(issue__issue_assignee__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ), diff --git a/apiserver/plane/app/views/issue/archive.py b/apiserver/plane/app/views/issue/archive.py index 283f1ad99..46815296d 100644 --- a/apiserver/plane/app/views/issue/archive.py +++ b/apiserver/plane/app/views/issue/archive.py @@ -67,7 +67,7 @@ class IssueArchiveViewSet(BaseViewSet): .annotate( cycle_id=Case( When( - issue_cycle__cycle__deleted_at__isnull=True, + issue_cycle__deleted_at__isnull=True, then=F("issue_cycle__cycle_id"), ), default=None, diff --git a/apiserver/plane/app/views/issue/base.py b/apiserver/plane/app/views/issue/base.py index 0488a5471..7bc3dd10e 100644 --- a/apiserver/plane/app/views/issue/base.py +++ b/apiserver/plane/app/views/issue/base.py @@ -88,7 +88,7 @@ class IssueListEndpoint(BaseAPIView): .annotate( cycle_id=Case( When( - issue_cycle__cycle__deleted_at__isnull=True, + issue_cycle__deleted_at__isnull=True, then=F("issue_cycle__cycle_id"), ), default=None, @@ -220,7 +220,7 @@ class IssueViewSet(BaseViewSet): .annotate( cycle_id=Case( When( - issue_cycle__cycle__deleted_at__isnull=True, + issue_cycle__deleted_at__isnull=True, then=F("issue_cycle__cycle_id"), ), default=None, @@ -489,9 +489,9 @@ class IssueViewSet(BaseViewSet): ArrayAgg( "labels__id", distinct=True, - filter=( + filter=Q( ~Q(labels__id__isnull=True) - & Q(labels__deleted_at__isnull=True) + & Q(label_issue__deleted_at__isnull=True), ), ), Value([], output_field=ArrayField(UUIDField())), @@ -500,8 +500,11 @@ class IssueViewSet(BaseViewSet): ArrayAgg( "assignees__id", distinct=True, - filter=~Q(assignees__id__isnull=True) - & Q(assignees__member_project__is_active=True), + filter=Q( + ~Q(assignees__id__isnull=True) + & Q(assignees__member_project__is_active=True) + & Q(issue_assignee__deleted_at__isnull=True) + ), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -509,9 +512,11 @@ class IssueViewSet(BaseViewSet): ArrayAgg( "issue_module__module_id", distinct=True, - filter=~Q(issue_module__module_id__isnull=True) - & Q(issue_module__module__archived_at__isnull=True) - & Q(issue_module__module__deleted_at__isnull=True), + filter=Q( + ~Q(issue_module__module_id__isnull=True) + & Q(issue_module__module__archived_at__isnull=True) + & Q(issue_module__deleted_at__isnull=True) + ), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -590,9 +595,9 @@ class IssueViewSet(BaseViewSet): ArrayAgg( "labels__id", distinct=True, - filter=( + filter=Q( ~Q(labels__id__isnull=True) - & Q(labels__deleted_at__isnull=True) + & Q(label_issue__deleted_at__isnull=True), ), ), Value([], output_field=ArrayField(UUIDField())), @@ -601,8 +606,11 @@ class IssueViewSet(BaseViewSet): ArrayAgg( "assignees__id", distinct=True, - filter=~Q(assignees__id__isnull=True) - & Q(assignees__member_project__is_active=True), + filter=Q( + ~Q(assignees__id__isnull=True) + & Q(assignees__member_project__is_active=True) + & Q(issue_assignee__deleted_at__isnull=True) + ), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -610,9 +618,11 @@ class IssueViewSet(BaseViewSet): ArrayAgg( "issue_module__module_id", distinct=True, - filter=~Q(issue_module__module_id__isnull=True) - & Q(issue_module__module__archived_at__isnull=True) - & Q(issue_module__module__deleted_at__isnull=True), + filter=Q( + ~Q(issue_module__module_id__isnull=True) + & Q(issue_module__module__archived_at__isnull=True) + & Q(issue_module__deleted_at__isnull=True) + ), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -778,7 +788,7 @@ class IssuePaginatedViewSet(BaseViewSet): .annotate( cycle_id=Case( When( - issue_cycle__cycle__deleted_at__isnull=True, + issue_cycle__deleted_at__isnull=True, then=F("issue_cycle__cycle_id"), ), default=None, @@ -890,9 +900,9 @@ class IssuePaginatedViewSet(BaseViewSet): ArrayAgg( "labels__id", distinct=True, - filter=( + filter=Q( ~Q(labels__id__isnull=True) - & Q(labels__deleted_at__isnull=True) + & Q(label_issue__deleted_at__isnull=True), ), ), Value([], output_field=ArrayField(UUIDField())), @@ -901,8 +911,11 @@ class IssuePaginatedViewSet(BaseViewSet): ArrayAgg( "assignees__id", distinct=True, - filter=~Q(assignees__id__isnull=True) - & Q(assignees__member_project__is_active=True), + filter=Q( + ~Q(assignees__id__isnull=True) + & Q(assignees__member_project__is_active=True) + & Q(issue_assignee__deleted_at__isnull=True) + ), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -910,9 +923,11 @@ class IssuePaginatedViewSet(BaseViewSet): ArrayAgg( "issue_module__module_id", distinct=True, - filter=~Q(issue_module__module_id__isnull=True) - & Q(issue_module__module__archived_at__isnull=True) - & Q(issue_module__module__deleted_at__isnull=True), + filter=Q( + ~Q(issue_module__module_id__isnull=True) + & Q(issue_module__module__archived_at__isnull=True) + & Q(issue_module__deleted_at__isnull=True) + ), ), Value([], output_field=ArrayField(UUIDField())), ), diff --git a/apiserver/plane/app/views/issue/relation.py b/apiserver/plane/app/views/issue/relation.py index 83385d83d..20243a11c 100644 --- a/apiserver/plane/app/views/issue/relation.py +++ b/apiserver/plane/app/views/issue/relation.py @@ -96,7 +96,7 @@ class IssueRelationViewSet(BaseViewSet): .annotate( cycle_id=Case( When( - issue_cycle__cycle__deleted_at__isnull=True, + issue_cycle__deleted_at__isnull=True, then=F("issue_cycle__cycle_id"), ), default=None, @@ -130,9 +130,9 @@ class IssueRelationViewSet(BaseViewSet): ArrayAgg( "labels__id", distinct=True, - filter=( + filter=Q( ~Q(labels__id__isnull=True) - & Q(labels__deleted_at__isnull=True) + & (Q(label_issue__deleted_at__isnull=True)) ), ), Value([], output_field=ArrayField(UUIDField())), @@ -141,8 +141,9 @@ class IssueRelationViewSet(BaseViewSet): ArrayAgg( "assignees__id", distinct=True, - filter=~Q(assignees__id__isnull=True) - & Q(assignees__member_project__is_active=True), + filter=Q(~Q(assignees__id__isnull=True) + & Q(assignees__member_project__is_active=True) + & Q(issue_assignee__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -289,7 +290,7 @@ class IssueRelationViewSet(BaseViewSet): IssueRelationSerializer(issue_relation).data, cls=DjangoJSONEncoder, ) - issue_relation.delete(soft=False) + issue_relation.delete() issue_activity.delay( type="issue_relation.activity.deleted", requested_data=json.dumps(request.data, cls=DjangoJSONEncoder), diff --git a/apiserver/plane/app/views/issue/sub_issue.py b/apiserver/plane/app/views/issue/sub_issue.py index 700d6db5b..02f3b6147 100644 --- a/apiserver/plane/app/views/issue/sub_issue.py +++ b/apiserver/plane/app/views/issue/sub_issue.py @@ -53,7 +53,7 @@ class SubIssuesEndpoint(BaseAPIView): .annotate( cycle_id=Case( When( - issue_cycle__cycle__deleted_at__isnull=True, + issue_cycle__deleted_at__isnull=True, then=F("issue_cycle__cycle_id"), ), default=None, @@ -87,9 +87,9 @@ class SubIssuesEndpoint(BaseAPIView): ArrayAgg( "labels__id", distinct=True, - filter=( + filter=Q( ~Q(labels__id__isnull=True) - & Q(labels__deleted_at__isnull=True) + & Q(label_issue__deleted_at__isnull=True), ), ), Value([], output_field=ArrayField(UUIDField())), @@ -98,8 +98,11 @@ class SubIssuesEndpoint(BaseAPIView): ArrayAgg( "assignees__id", distinct=True, - filter=~Q(assignees__id__isnull=True) - & Q(assignees__member_project__is_active=True), + filter=Q( + ~Q(assignees__id__isnull=True) + & Q(assignees__member_project__is_active=True) + & Q(issue_assignee__deleted_at__isnull=True) + ), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -107,9 +110,11 @@ class SubIssuesEndpoint(BaseAPIView): ArrayAgg( "issue_module__module_id", distinct=True, - filter=~Q(issue_module__module_id__isnull=True) - & Q(issue_module__module__archived_at__isnull=True) - & Q(issue_module__module__deleted_at__isnull=True), + filter=Q( + ~Q(issue_module__module_id__isnull=True) + & Q(issue_module__module__archived_at__isnull=True) + & Q(issue_module__deleted_at__isnull=True) + ), ), Value([], output_field=ArrayField(UUIDField())), ), diff --git a/apiserver/plane/app/views/module/issue.py b/apiserver/plane/app/views/module/issue.py index f58e47756..ae59efbca 100644 --- a/apiserver/plane/app/views/module/issue.py +++ b/apiserver/plane/app/views/module/issue.py @@ -70,7 +70,7 @@ class ModuleIssueViewSet(BaseViewSet): .annotate( cycle_id=Case( When( - issue_cycle__cycle__deleted_at__isnull=True, + issue_cycle__deleted_at__isnull=True, then=F("issue_cycle__cycle_id"), ), default=None, diff --git a/apiserver/plane/app/views/view/base.py b/apiserver/plane/app/views/view/base.py index 59cfcecd5..a14e761a5 100644 --- a/apiserver/plane/app/views/view/base.py +++ b/apiserver/plane/app/views/view/base.py @@ -210,7 +210,7 @@ class WorkspaceViewIssuesViewSet(BaseViewSet): .annotate( cycle_id=Case( When( - issue_cycle__cycle__deleted_at__isnull=True, + issue_cycle__deleted_at__isnull=True, then=F("issue_cycle__cycle_id"), ), default=None, @@ -244,9 +244,9 @@ class WorkspaceViewIssuesViewSet(BaseViewSet): ArrayAgg( "labels__id", distinct=True, - filter=( + filter=Q( ~Q(labels__id__isnull=True) - & Q(labels__deleted_at__isnull=True) + & Q(label_issue__deleted_at__isnull=True), ), ), Value([], output_field=ArrayField(UUIDField())), @@ -255,8 +255,11 @@ class WorkspaceViewIssuesViewSet(BaseViewSet): ArrayAgg( "assignees__id", distinct=True, - filter=~Q(assignees__id__isnull=True) - & Q(assignees__member_project__is_active=True), + filter=Q( + ~Q(assignees__id__isnull=True) + & Q(assignees__member_project__is_active=True) + & Q(issue_assignee__deleted_at__isnull=True) + ), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -264,9 +267,11 @@ class WorkspaceViewIssuesViewSet(BaseViewSet): ArrayAgg( "issue_module__module_id", distinct=True, - filter=~Q(issue_module__module_id__isnull=True) - & Q(issue_module__module__archived_at__isnull=True) - & Q(issue_module__module__deleted_at__isnull=True), + filter=Q( + ~Q(issue_module__module_id__isnull=True) + & Q(issue_module__module__archived_at__isnull=True) + & Q(issue_module__deleted_at__isnull=True) + ), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -288,7 +293,7 @@ class WorkspaceViewIssuesViewSet(BaseViewSet): .annotate( cycle_id=Case( When( - issue_cycle__cycle__deleted_at__isnull=True, + issue_cycle__deleted_at__isnull=True, then=F("issue_cycle__cycle_id"), ), default=None, @@ -552,7 +557,9 @@ class IssueViewViewSet(BaseViewSet): serializer.errors, status=status.HTTP_400_BAD_REQUEST ) - @allow_permission(allowed_roles=[ROLE.ADMIN], creator=True, model=IssueView) + @allow_permission( + allowed_roles=[ROLE.ADMIN], creator=True, model=IssueView + ) def destroy(self, request, slug, project_id, pk): project_view = IssueView.objects.get( pk=pk, diff --git a/apiserver/plane/app/views/workspace/draft.py b/apiserver/plane/app/views/workspace/draft.py index ae7db2a40..265ef111c 100644 --- a/apiserver/plane/app/views/workspace/draft.py +++ b/apiserver/plane/app/views/workspace/draft.py @@ -60,7 +60,7 @@ class WorkspaceDraftIssueViewSet(BaseViewSet): .annotate( cycle_id=Case( When( - draft_issue_cycle__cycle__deleted_at__isnull=True, + draft_issue_cycle__deleted_at__isnull=True, then=F("draft_issue_cycle__cycle_id"), ), default=None, @@ -71,9 +71,9 @@ class WorkspaceDraftIssueViewSet(BaseViewSet): ArrayAgg( "labels__id", distinct=True, - filter=( + filter=Q( ~Q(labels__id__isnull=True) - & Q(labels__deleted_at__isnull=True) + & (Q(draft_label_issue__deleted_at__isnull=True)) ), ), Value([], output_field=ArrayField(UUIDField())), @@ -82,8 +82,9 @@ class WorkspaceDraftIssueViewSet(BaseViewSet): ArrayAgg( "assignees__id", distinct=True, - filter=~Q(assignees__id__isnull=True) - & Q(assignees__member_project__is_active=True), + filter=Q(~Q(assignees__id__isnull=True) + & Q(assignees__member_project__is_active=True) + & Q(draft_issue_assignee__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -91,13 +92,11 @@ class WorkspaceDraftIssueViewSet(BaseViewSet): ArrayAgg( "draft_issue_module__module_id", distinct=True, - filter=~Q(draft_issue_module__module_id__isnull=True) + filter=Q(~Q(draft_issue_module__module_id__isnull=True) & Q( draft_issue_module__module__archived_at__isnull=True ) - & Q( - draft_issue_module__module__deleted_at__isnull=True - ), + & Q(draft_issue_module__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ), diff --git a/apiserver/plane/app/views/workspace/user.py b/apiserver/plane/app/views/workspace/user.py index fae917af2..a69f18e19 100644 --- a/apiserver/plane/app/views/workspace/user.py +++ b/apiserver/plane/app/views/workspace/user.py @@ -123,7 +123,7 @@ class WorkspaceUserProfileIssuesEndpoint(BaseAPIView): .annotate( cycle_id=Case( When( - issue_cycle__cycle__deleted_at__isnull=True, + issue_cycle__deleted_at__isnull=True, then=F("issue_cycle__cycle_id"), ), default=None, diff --git a/apiserver/plane/bgtasks/analytic_plot_export.py b/apiserver/plane/bgtasks/analytic_plot_export.py index 7d78b89d0..a4341841e 100644 --- a/apiserver/plane/bgtasks/analytic_plot_export.py +++ b/apiserver/plane/bgtasks/analytic_plot_export.py @@ -130,7 +130,9 @@ def get_label_details(slug, filters): """Fetch label details if required""" return ( Issue.objects.filter( - workspace__slug=slug, **filters, labels__id__isnull=False + workspace__slug=slug, + **filters, + labels__id__isnull=False & Q(label_issue__deleted_at__isnull=True), ) .distinct("labels__id") .order_by("labels__id") diff --git a/apiserver/plane/celery.py b/apiserver/plane/celery.py index 865ef95fa..c58844de3 100644 --- a/apiserver/plane/celery.py +++ b/apiserver/plane/celery.py @@ -32,14 +32,14 @@ app.conf.beat_schedule = { "task": "plane.bgtasks.email_notification_task.stack_email_notification", "schedule": crontab(minute="*/5"), }, - "check-every-day-to-delete-api-logs": { - "task": "plane.bgtasks.api_logs_task.delete_api_logs", - "schedule": crontab(hour=0, minute=0), - }, "check-every-day-to-delete-hard-delete": { "task": "plane.bgtasks.deletion_task.hard_delete", "schedule": crontab(hour=0, minute=0), }, + "check-every-day-to-delete-api-logs": { + "task": "plane.bgtasks.api_logs_task.delete_api_logs", + "schedule": crontab(hour=0, minute=0), + }, "run-every-6-hours-for-instance-trace": { "task": "plane.license.bgtasks.tracer.instance_traces", "schedule": crontab(hour="*/6"), diff --git a/apiserver/plane/db/migrations/0082_alter_issue_managers_alter_cycleissue_issue_and_more.py b/apiserver/plane/db/migrations/0082_alter_issue_managers_alter_cycleissue_issue_and_more.py new file mode 100644 index 000000000..9d0279eb4 --- /dev/null +++ b/apiserver/plane/db/migrations/0082_alter_issue_managers_alter_cycleissue_issue_and_more.py @@ -0,0 +1,63 @@ +# Generated by Django 4.2.15 on 2024-10-22 08:00 + +from django.db import migrations, models +import django.db.models.deletion +import django.db.models.manager + + +class Migration(migrations.Migration): + + dependencies = [ + ("db", "0081_remove_globalview_created_by_and_more"), + ] + + operations = [ + migrations.AlterModelManagers( + name="issue", + managers=[ + ("issue_objects", django.db.models.manager.Manager()), + ], + ), + migrations.AlterField( + model_name="cycleissue", + name="issue", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="issue_cycle", + to="db.issue", + ), + ), + migrations.AlterField( + model_name="draftissuecycle", + name="draft_issue", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="draft_issue_cycle", + to="db.draftissue", + ), + ), + migrations.AlterUniqueTogether( + name="cycleissue", + unique_together={("issue", "cycle", "deleted_at")}, + ), + migrations.AlterUniqueTogether( + name="draftissuecycle", + unique_together={("draft_issue", "cycle", "deleted_at")}, + ), + migrations.AddConstraint( + model_name="cycleissue", + constraint=models.UniqueConstraint( + condition=models.Q(("deleted_at__isnull", True)), + fields=("cycle", "issue"), + name="cycle_issue_when_deleted_at_null", + ), + ), + migrations.AddConstraint( + model_name="draftissuecycle", + constraint=models.UniqueConstraint( + condition=models.Q(("deleted_at__isnull", True)), + fields=("draft_issue", "cycle"), + name="draft_issue_cycle_when_deleted_at_null", + ), + ), + ] diff --git a/apiserver/plane/db/models/cycle.py b/apiserver/plane/db/models/cycle.py index f9f9eece9..c3dccb0c8 100644 --- a/apiserver/plane/db/models/cycle.py +++ b/apiserver/plane/db/models/cycle.py @@ -110,7 +110,7 @@ class CycleIssue(ProjectBaseModel): Cycle Issues """ - issue = models.OneToOneField( + issue = models.ForeignKey( "db.Issue", on_delete=models.CASCADE, related_name="issue_cycle" ) cycle = models.ForeignKey( @@ -118,6 +118,14 @@ class CycleIssue(ProjectBaseModel): ) class Meta: + unique_together = ["issue", "cycle", "deleted_at"] + constraints = [ + models.UniqueConstraint( + fields=["cycle", "issue"], + condition=models.Q(deleted_at__isnull=True), + name="cycle_issue_when_deleted_at_null", + ) + ] verbose_name = "Cycle Issue" verbose_name_plural = "Cycle Issues" db_table = "cycle_issues" diff --git a/apiserver/plane/db/models/draft.py b/apiserver/plane/db/models/draft.py index 671b89ff1..e80ccdaaf 100644 --- a/apiserver/plane/db/models/draft.py +++ b/apiserver/plane/db/models/draft.py @@ -234,7 +234,7 @@ class DraftIssueCycle(WorkspaceBaseModel): Draft Issue Cycles """ - draft_issue = models.OneToOneField( + draft_issue = models.ForeignKey( "db.DraftIssue", on_delete=models.CASCADE, related_name="draft_issue_cycle", @@ -244,6 +244,14 @@ class DraftIssueCycle(WorkspaceBaseModel): ) class Meta: + unique_together = ["draft_issue", "cycle", "deleted_at"] + constraints = [ + models.UniqueConstraint( + fields=["draft_issue", "cycle"], + condition=models.Q(deleted_at__isnull=True), + name="draft_issue_cycle_when_deleted_at_null", + ) + ] verbose_name = "Draft Issue Cycle" verbose_name_plural = "Draft Issue Cycles" db_table = "draft_issue_cycles" diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index 7ff9af46e..4bc529f55 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -91,6 +91,7 @@ class IssueManager(SoftDeletionManager): | models.Q(issue_inbox__status=2) | models.Q(issue_inbox__isnull=True) ) + .filter(deleted_at__isnull=True) .filter(state__is_triage=False) .exclude(archived_at__isnull=False) .exclude(project__archived_at__isnull=False) diff --git a/apiserver/plane/space/serializer/issue.py b/apiserver/plane/space/serializer/issue.py index cf628e850..d7447681f 100644 --- a/apiserver/plane/space/serializer/issue.py +++ b/apiserver/plane/space/serializer/issue.py @@ -421,7 +421,7 @@ class IssueCreateSerializer(BaseSerializer): updated_by_id = instance.updated_by_id if assignees is not None: - IssueAssignee.objects.filter(issue=instance).delete(soft=False) + IssueAssignee.objects.filter(issue=instance).delete() IssueAssignee.objects.bulk_create( [ IssueAssignee( @@ -438,7 +438,7 @@ class IssueCreateSerializer(BaseSerializer): ) if labels is not None: - IssueLabel.objects.filter(issue=instance).delete(soft=False) + IssueLabel.objects.filter(issue=instance).delete() IssueLabel.objects.bulk_create( [ IssueLabel( diff --git a/apiserver/plane/space/utils/grouper.py b/apiserver/plane/space/utils/grouper.py index a1eb16b9c..680359790 100644 --- a/apiserver/plane/space/utils/grouper.py +++ b/apiserver/plane/space/utils/grouper.py @@ -35,8 +35,16 @@ def issue_queryset_grouper(queryset, group_by, sub_group_by): } annotations_map = { - "assignee_ids": ("assignees__id", ~Q(assignees__id__isnull=True)), - "label_ids": ("labels__id", ~Q(labels__id__isnull=True)), + "assignee_ids": ( + "assignees__id", + ~Q(assignees__id__isnull=True) + & Q(issue_assignee__deleted_at__isnull=True), + ), + "label_ids": ( + "labels__id", + ~Q(labels__id__isnull=True) + & Q(label_issue__deleted_at__isnull=True), + ), "module_ids": ( "issue_module__module_id", ~Q(issue_module__module_id__isnull=True), diff --git a/apiserver/plane/space/views/issue.py b/apiserver/plane/space/views/issue.py index bd9d499d6..606664b01 100644 --- a/apiserver/plane/space/views/issue.py +++ b/apiserver/plane/space/views/issue.py @@ -109,7 +109,7 @@ class ProjectIssuesPublicEndpoint(BaseAPIView): .annotate( cycle_id=Case( When( - issue_cycle__cycle__deleted_at__isnull=True, + issue_cycle__deleted_at__isnull=True, then=F("issue_cycle__cycle_id"), ), default=None, @@ -706,7 +706,7 @@ class IssueRetrievePublicEndpoint(BaseAPIView): .annotate( cycle_id=Case( When( - issue_cycle__cycle__deleted_at__isnull=True, + issue_cycle__deleted_at__isnull=True, then=F("issue_cycle__cycle_id"), ), default=None, @@ -717,9 +717,9 @@ class IssueRetrievePublicEndpoint(BaseAPIView): ArrayAgg( "labels__id", distinct=True, - filter=( + filter=Q( ~Q(labels__id__isnull=True) - & Q(labels__deleted_at__isnull=True) + & Q(label_issue__deleted_at__isnull=True), ), ), Value([], output_field=ArrayField(UUIDField())), @@ -728,8 +728,9 @@ class IssueRetrievePublicEndpoint(BaseAPIView): ArrayAgg( "assignees__id", distinct=True, - filter=~Q(assignees__id__isnull=True) - & Q(assignees__member_project__is_active=True), + filter=Q(~Q(assignees__id__isnull=True) + & Q(assignees__member_project__is_active=True) + & Q(issue_assignee__deleted_at__isnull=True)), ), Value([], output_field=ArrayField(UUIDField())), ), @@ -739,7 +740,7 @@ class IssueRetrievePublicEndpoint(BaseAPIView): distinct=True, filter=~Q(issue_module__module_id__isnull=True) & Q(issue_module__module__archived_at__isnull=True) - & Q(issue_module__module__deleted_at__isnull=True), + & Q(issue_module__deleted_at__isnull=True), ), Value([], output_field=ArrayField(UUIDField())), ), diff --git a/apiserver/plane/utils/grouper.py b/apiserver/plane/utils/grouper.py index fef47e0b0..38ac74a16 100644 --- a/apiserver/plane/utils/grouper.py +++ b/apiserver/plane/utils/grouper.py @@ -25,17 +25,22 @@ def issue_queryset_grouper(queryset, group_by, sub_group_by): } annotations_map = { - "assignee_ids": ("assignees__id", ~Q(assignees__id__isnull=True)), + "assignee_ids": ( + "assignees__id", + ~Q(assignees__id__isnull=True) + & Q(issue_assignee__deleted_at__isnull=True), + ), "label_ids": ( "labels__id", - ~Q(labels__id__isnull=True) & (Q(labels__deleted_at__isnull=True)), + ~Q(labels__id__isnull=True) + & Q(label_issue__deleted_at__isnull=True), ), "module_ids": ( "issue_module__module_id", ( ~Q(issue_module__module_id__isnull=True) & Q(issue_module__module__archived_at__isnull=True) - & Q(issue_module__module__deleted_at__isnull=True) + & Q(issue_module__deleted_at__isnull=True) ), ), } diff --git a/apiserver/plane/utils/issue_filters.py b/apiserver/plane/utils/issue_filters.py index 713276d0c..b82ba1e8c 100644 --- a/apiserver/plane/utils/issue_filters.py +++ b/apiserver/plane/utils/issue_filters.py @@ -48,14 +48,22 @@ def string_date_filter( if term == "weeks": if subsequent == "after": if offset == "fromnow": - issue_filter[f"{date_filter}__gte"] = now + timedelta(weeks=duration) + issue_filter[f"{date_filter}__gte"] = now + timedelta( + weeks=duration + ) else: - issue_filter[f"{date_filter}__gte"] = now - timedelta(weeks=duration) + issue_filter[f"{date_filter}__gte"] = now - timedelta( + weeks=duration + ) else: if offset == "fromnow": - issue_filter[f"{date_filter}__lte"] = now + timedelta(weeks=duration) + issue_filter[f"{date_filter}__lte"] = now + timedelta( + weeks=duration + ) else: - issue_filter[f"{date_filter}__lte"] = now - timedelta(weeks=duration) + issue_filter[f"{date_filter}__lte"] = now - timedelta( + weeks=duration + ) def date_filter(issue_filter, date_term, queries): @@ -120,7 +128,9 @@ def filter_state_group(params, issue_filter, method, prefix=""): and len(params.get("state_group")) and params.get("state_group") != "null" ): - issue_filter[f"{prefix}state__group__in"] = params.get("state_group") + issue_filter[f"{prefix}state__group__in"] = params.get( + "state_group" + ) return issue_filter @@ -242,8 +252,8 @@ def filter_mentions(params, issue_filter, method, prefix=""): and len(params.get("mentions")) and params.get("mentions") != "null" ): - issue_filter[f"{prefix}issue_mention__mention__id__in"] = params.get( - "mentions" + issue_filter[f"{prefix}issue_mention__mention__id__in"] = ( + params.get("mentions") ) return issue_filter @@ -411,7 +421,10 @@ def filter_cycle(params, issue_filter, method, prefix=""): and len(params.get("cycle")) and params.get("cycle") != "null" ): - issue_filter[f"{prefix}issue_cycle__cycle_id__in"] = params.get("cycle") + issue_filter[f"{prefix}issue_cycle__cycle_id__in"] = params.get( + "cycle" + ) + issue_filter[f"{prefix}issue_cycle__deleted_at__isnull"] = True return issue_filter @@ -434,6 +447,7 @@ def filter_module(params, issue_filter, method, prefix=""): issue_filter[f"{prefix}issue_module__module_id__in"] = params.get( "module" ) + issue_filter[f"{prefix}issue_module__deleted_at__isnull"] = True return issue_filter