Compare commits

..
Author SHA1 Message Date
rahulramesha 28c9245fe1 modify changes to test parallel group calls 2024-07-29 15:23:37 +05:30
31 changed files with 198 additions and 759 deletions
-2
View File
@@ -166,7 +166,6 @@ class IssueListEndpoint(BaseAPIView):
"link_count",
"is_draft",
"archived_at",
"deleted_at",
)
datetime_fields = ["created_at", "updated_at"]
issues = user_timezone_converter(
@@ -401,7 +400,6 @@ class IssueViewSet(BaseViewSet):
"link_count",
"is_draft",
"archived_at",
"deleted_at",
)
.first()
)
@@ -100,8 +100,10 @@ class OauthAdapter(Adapter):
account, created = Account.objects.update_or_create(
user=user,
provider=self.provider,
provider_account_id=self.user_data.get("user").get("provider_id"),
defaults={
"provider_account_id": self.user_data.get("user").get(
"provider_id"
),
"access_token": self.token_data.get("access_token"),
"refresh_token": self.token_data.get("refresh_token", None),
"access_token_expired_at": self.token_data.get(
-158
View File
@@ -1,158 +0,0 @@
# Django imports
from django.utils import timezone
from django.apps import apps
from django.core.exceptions import ObjectDoesNotExist
# Third party imports
from celery import shared_task
@shared_task
def soft_delete_related_objects(
app_label, model_name, instance_pk, using=None
):
model_class = apps.get_model(app_label, model_name)
instance = model_class.all_objects.get(pk=instance_pk)
related_fields = instance._meta.get_fields()
for field in related_fields:
if field.one_to_many or field.one_to_one or field.many_to_many:
try:
if field.one_to_many or field.many_to_many:
related_objects = getattr(instance, field.name).all()
elif field.one_to_one:
related_object = getattr(instance, field.name)
related_objects = (
[related_object] if related_object is not None else []
)
for obj in related_objects:
if obj:
obj.deleted_at = timezone.now()
obj.save(using=using)
except ObjectDoesNotExist:
pass
# @shared_task
def restore_related_objects(app_label, model_name, instance_pk, using=None):
pass
@shared_task
def hard_delete():
from plane.db.models import (
Workspace,
Project,
Cycle,
Module,
Issue,
Page,
IssueView,
Label,
State,
IssueActivity,
IssueComment,
IssueLink,
IssueReaction,
UserFavorite,
ModuleIssue,
CycleIssue,
Estimate,
EstimatePoint
)
# check delete workspace
_ = Workspace.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
# check delete project
_ = Project.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
# check delete cycle
_ = Cycle.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
# check delete module
_ = Module.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
# check delete issue
_ = Issue.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
# check delete page
_ = Page.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
# check delete view
_ = IssueView.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
# check delete label
_ = Label.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
# check delete state
_ = State.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
_ = IssueActivity.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
_ = IssueComment.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
_ = IssueLink.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
_ = IssueReaction.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
_ = UserFavorite.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
_ = ModuleIssue.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
_ = CycleIssue.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
_ = Estimate.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
_ = EstimatePoint.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
# at last, check for every thing which ever is left and delete it
# Get all Django models
all_models = apps.get_models()
# Iterate through all models
for model in all_models:
# Check if the model has a 'deleted_at' field
if hasattr(model, "deleted_at"):
# Get all instances where 'deleted_at' is greater than 30 days ago
_ = model.all_objects.filter(
deleted_at__lt=timezone.now() - timezone.timedelta(days=30)
).delete()
return
@@ -221,6 +221,7 @@ def notifications(
else None
)
if type not in [
"issue.activity.deleted",
"cycle.activity.created",
"cycle.activity.deleted",
"module.activity.created",
-4
View File
@@ -36,10 +36,6 @@ app.conf.beat_schedule = {
"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),
},
}
# Load task modules from all registered Django app configs.
@@ -1,423 +0,0 @@
# Generated by Django 4.2.11 on 2024-07-26 11:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('db', '0072_issueattachment_external_id_and_more'),
]
operations = [
migrations.AddField(
model_name='analyticview',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='apiactivitylog',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='apitoken',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='commentreaction',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='cycle',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='cyclefavorite',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='cycleissue',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='cycleuserproperties',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='dashboard',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='dashboardwidget',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='deployboard',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='emailnotificationlog',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='estimate',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='estimatepoint',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='exporterhistory',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='fileasset',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='githubcommentsync',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='githubissuesync',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='githubrepository',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='githubrepositorysync',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='globalview',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='importer',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='inbox',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='inboxissue',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='integration',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='issue',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='issueactivity',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='issueassignee',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='issueattachment',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='issueblocker',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='issuecomment',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='issuelabel',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='issuelink',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='issuemention',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='issuereaction',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='issuerelation',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='issuesequence',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='issuesubscriber',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='issuetype',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='issueuserproperty',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='issueview',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='issueviewfavorite',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='issuevote',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='label',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='module',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='modulefavorite',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='moduleissue',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='modulelink',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='modulemember',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='moduleuserproperties',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='notification',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='page',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='pageblock',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='pagefavorite',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='pagelabel',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='pagelog',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='pageversion',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='project',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='projectdeployboard',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='projectfavorite',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='projectidentifier',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='projectmember',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='projectmemberinvite',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='projectpage',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='projectpublicmember',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='slackprojectsync',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='socialloginconnection',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='state',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='team',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='teammember',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='teampage',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='userfavorite',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='usernotificationpreference',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='userrecentvisit',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='webhook',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='webhooklog',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='workspace',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='workspaceintegration',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='workspacemember',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='workspacememberinvite',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='workspacetheme',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='workspaceuserproperties',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
]
+3 -43
View File
@@ -1,9 +1,7 @@
# Python imports
# Django imports
from django.db import models
from django.utils import timezone
# Module imports
from plane.bgtasks.deletion_task import soft_delete_related_objects
class TimeAuditModel(models.Model):
@@ -43,45 +41,7 @@ class UserAuditModel(models.Model):
abstract = True
class SoftDeletionManager(models.Manager):
def get_queryset(self):
return super().get_queryset().filter(deleted_at__isnull=True)
class SoftDeleteModel(models.Model):
"""To soft delete records"""
deleted_at = models.DateTimeField(
verbose_name="Deleted At",
null=True,
blank=True,
)
objects = SoftDeletionManager()
all_objects = models.Manager()
class Meta:
abstract = True
def delete(self, using=None, soft=True, *args, **kwargs):
if soft:
# Soft delete the current instance
self.deleted_at = timezone.now()
self.save(using=using)
soft_delete_related_objects.delay(
self._meta.app_label,
self._meta.model_name,
self.pk,
using=using,
)
else:
# Perform hard delete if soft deletion is not enabled
return super().delete(using=using, *args, **kwargs)
class AuditModel(TimeAuditModel, UserAuditModel, SoftDeleteModel):
class AuditModel(TimeAuditModel, UserAuditModel):
"""To path when the record was created and last modified"""
class Meta:
-1
View File
@@ -116,7 +116,6 @@ class CycleIssue(ProjectBaseModel):
return f"{self.cycle}"
# DEPRECATED TODO: - Remove in next release
class CycleFavorite(ProjectBaseModel):
"""_summary_
CycleFavorite (model): To store all the cycle favorite of the user
-1
View File
@@ -89,7 +89,6 @@ class IssueManager(models.Manager):
| 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)
-1
View File
@@ -169,7 +169,6 @@ class ModuleLink(ProjectBaseModel):
return f"{self.module.name} {self.url}"
# DEPRECATED TODO: - Remove in next release
class ModuleFavorite(ProjectBaseModel):
"""_summary_
ModuleFavorite (model): To store all the module favorite of the user
-2
View File
@@ -119,7 +119,6 @@ class PageLog(BaseModel):
return f"{self.page.name} {self.entity_name}"
# DEPRECATED TODO: - Remove in next release
class PageBlock(ProjectBaseModel):
page = models.ForeignKey(
"db.Page", on_delete=models.CASCADE, related_name="blocks"
@@ -176,7 +175,6 @@ class PageBlock(ProjectBaseModel):
return f"{self.page.name} <{self.name}>"
# DEPRECATED TODO: - Remove in next release
class PageFavorite(ProjectBaseModel):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
-1
View File
@@ -230,7 +230,6 @@ class ProjectIdentifier(AuditModel):
ordering = ("-created_at",)
# DEPRECATED TODO: - Remove in next release
class ProjectFavorite(ProjectBaseModel):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
+1 -1
View File
@@ -52,6 +52,7 @@ def get_default_display_properties():
"updated_on": True,
}
# DEPRECATED TODO: - Remove in next release
class GlobalView(BaseModel):
workspace = models.ForeignKey(
@@ -141,7 +142,6 @@ class IssueView(WorkspaceBaseModel):
return f"{self.name} <{self.project.name}>"
# DEPRECATED TODO: - Remove in next release
class IssueViewFavorite(ProjectBaseModel):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
@@ -1,33 +0,0 @@
# Generated by Django 4.2.11 on 2024-07-26 11:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('license', '0003_alter_changelog_title_alter_changelog_version_and_more'),
]
operations = [
migrations.AddField(
model_name='changelog',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='instance',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='instanceadmin',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
migrations.AddField(
model_name='instanceconfiguration',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True, verbose_name='Deleted At'),
),
]
@@ -222,7 +222,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
const kanbanFilters = issuesFilter?.issueFilters?.kanbanFilters || { group_by: [], sub_group_by: [] };
return (
<IssueLayoutHOC layout={EIssueLayoutTypes.KANBAN}>
<>
<DeleteIssueModal
dataId={draggedIssueId}
isOpen={deleteIssueModal}
@@ -279,6 +279,6 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
</div>
</div>
</div>
</IssueLayoutHOC>
</>
);
});
@@ -156,7 +156,7 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
column_id={subList.id}
icon={subList.icon}
title={subList.name}
count={getGroupIssueCount(subList.id, undefined, false) ?? 0}
count={getGroupIssueCount(subList.id, undefined, false)}
issuePayload={subList.payload}
disableIssueCreation={disableIssueCreation || isGroupByCreatedBy}
addIssuesToView={addIssuesToView}
@@ -15,6 +15,7 @@ import { CreateUpdateIssueModal } from "@/components/issues";
// hooks
import { useEventTracker } from "@/hooks/store";
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
import isNil from "lodash/isNil";
// types
interface IHeaderGroupByCard {
@@ -23,7 +24,7 @@ interface IHeaderGroupByCard {
column_id: string;
icon?: React.ReactNode;
title: string;
count: number;
count: number | undefined;
kanbanFilters: TIssueKanbanFilters;
handleKanbanFilters: any;
issuePayload: Partial<TIssue>;
@@ -123,11 +124,13 @@ export const HeaderGroupByCard: FC<IHeaderGroupByCard> = observer((props) => {
>
{title}
</div>
<div
className={`flex-shrink-0 text-sm font-medium text-custom-text-300 ${verticalAlignPosition ? `` : `pl-2`}`}
>
{count || 0}
</div>
{!isNil(count) && (
<div
className={`flex-shrink-0 text-sm font-medium text-custom-text-300 ${verticalAlignPosition ? `` : `pl-2`}`}
>
{count || 0}
</div>
)}
</div>
{sub_group_by === null && (
@@ -28,11 +28,12 @@ import { GroupDragOverlay } from "../group-drag-overlay";
import { TRenderQuickActions } from "../list/list-view-types";
import { GroupDropLocation, getSourceFromDropPayload, getDestinationFromDropPayload, getIssueBlockId } from "../utils";
import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from ".";
import isNil from "lodash/isNil";
interface IKanbanGroup {
groupId: string;
issuesMap: IIssueMap;
groupedIssueIds: TGroupedIssues | TSubGroupedIssues;
groupedIssueIds: TGroupedIssues | TSubGroupedIssues;
displayProperties: IIssueDisplayProperties | undefined;
sub_group_by: TIssueGroupByOptions | undefined;
group_by: TIssueGroupByOptions | undefined;
@@ -88,8 +89,8 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
const containerRef = sub_group_by && scrollableContainerRef ? scrollableContainerRef : columnRef;
const loadMoreIssuesInThisGroup = useCallback(() => {
loadMoreIssues(groupId, sub_group_id === "null"? undefined: sub_group_id)
}, [loadMoreIssues, groupId, sub_group_id])
loadMoreIssues(groupId, sub_group_id === "null" ? undefined : sub_group_id);
}, [loadMoreIssues, groupId, sub_group_id]);
const isPaginating = !!getIssueLoader(groupId, sub_group_id);
@@ -218,11 +219,10 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
? (groupedIssueIds as TSubGroupedIssues)?.[groupId]?.[sub_group_id] ?? []
: (groupedIssueIds as TGroupedIssues)?.[groupId] ?? [];
const groupIssueCount = getGroupIssueCount(groupId, sub_group_id, false) ?? 0;
const groupIssueCount = getGroupIssueCount(groupId, sub_group_id, false);
const nextPageResults = getPaginationData(groupId, sub_group_id)?.nextPageResults;
const loadMore = isPaginating ? (
<KanbanIssueBlockLoader />
) : (
@@ -235,8 +235,9 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
</div>
);
const shouldLoadMore = nextPageResults === undefined ? issueIds?.length < groupIssueCount : !!nextPageResults;
const canOverlayBeVisible = orderBy !== "sort_order" || isDropDisabled;
const shouldLoadMore =
nextPageResults === undefined ? isNil(groupIssueCount) || issueIds?.length < groupIssueCount : !!nextPageResults;
const canOverlayBeVisible = orderBy !== "sort_order" || isDropDisabled;
const shouldOverlayBeVisible = isDraggingOverColumn && canOverlayBeVisible;
return (
@@ -270,7 +271,7 @@ export const KanbanGroup = observer((props: IKanbanGroup) => {
canDropOverIssue={!canOverlayBeVisible}
/>
{shouldLoadMore && (isSubGroup ? <>{loadMore}</> : <KanbanIssueBlockLoader ref={setIntersectionElement} />)}
{shouldLoadMore && (isSubGroup ? <>{loadMore}</> : <KanbanIssueBlockLoader ref={setIntersectionElement} />)}
{enableQuickIssueCreate && !disableIssueCreation && (
<div className="w-full bg-custom-background-90 py-0.5 sticky bottom-0">
@@ -56,9 +56,9 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = observer(
{list &&
list.length > 0 &&
list.map((_list: IGroupByColumn) => {
const groupCount = getGroupIssueCount(_list?.id, undefined, false) ?? 0;
const groupCount = getGroupIssueCount(_list?.id, undefined, false);
const subGroupByVisibilityToggle = visibilitySubGroupByGroupCount(groupCount, showEmptyGroup);
const subGroupByVisibilityToggle = visibilitySubGroupByGroupCount(groupCount ?? 0, showEmptyGroup);
if (subGroupByVisibilityToggle === false) return <></>;
@@ -107,27 +107,25 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
);
return (
<IssueLayoutHOC layout={EIssueLayoutTypes.LIST}>
<div className={`relative size-full bg-custom-background-90`}>
<List
issuesMap={issueMap}
displayProperties={displayProperties}
group_by={group_by}
orderBy={orderBy}
updateIssue={updateIssue}
quickActions={renderQuickActions}
groupedIssueIds={groupedIssueIds ?? {}}
loadMoreIssues={loadMoreIssues}
showEmptyGroup={showEmptyGroup}
quickAddCallback={quickAddIssue}
enableIssueQuickAdd={!!enableQuickAdd}
canEditProperties={canEditProperties}
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
addIssuesToView={addIssuesToView}
isCompletedCycle={isCompletedCycle}
handleOnDrop={handleOnDrop}
/>
</div>
</IssueLayoutHOC>
<div className={`relative size-full bg-custom-background-90`}>
<List
issuesMap={issueMap}
displayProperties={displayProperties}
group_by={group_by}
orderBy={orderBy}
updateIssue={updateIssue}
quickActions={renderQuickActions}
groupedIssueIds={groupedIssueIds ?? {}}
loadMoreIssues={loadMoreIssues}
showEmptyGroup={showEmptyGroup}
quickAddCallback={quickAddIssue}
enableIssueQuickAdd={!!enableQuickAdd}
canEditProperties={canEditProperties}
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
addIssuesToView={addIssuesToView}
isCompletedCycle={isCompletedCycle}
handleOnDrop={handleOnDrop}
/>
</div>
);
});
@@ -18,12 +18,13 @@ import { cn } from "@/helpers/common.helper";
import { useEventTracker } from "@/hooks/store";
import { useIssueStoreType } from "@/hooks/use-issue-layout-store";
import { TSelectionHelper } from "@/hooks/use-multiple-select";
import isNil from "lodash/isNil";
interface IHeaderGroupByCard {
groupID: string;
icon?: React.ReactNode;
title: string;
count: number;
count: number | undefined;
issuePayload: Partial<TIssue>;
canEditProperties: (projectId: string | undefined) => boolean;
toggleListGroup: () => void;
@@ -98,7 +99,7 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
)}
groupID={groupID}
selectionHelpers={selectionHelpers}
disabled={count === 0}
disabled={!count}
/>
</div>
)}
@@ -106,10 +107,12 @@ export const HeaderGroupByCard = observer((props: IHeaderGroupByCard) => {
{icon ?? <CircleDashed className="size-3.5" strokeWidth={2} />}
</div>
<div className="relative flex w-full flex-row items-center gap-1 overflow-hidden cursor-pointer"
onClick={toggleListGroup}>
<div
className="relative flex w-full flex-row items-center gap-1 overflow-hidden cursor-pointer"
onClick={toggleListGroup}
>
<div className="inline-block line-clamp-1 truncate font-medium text-custom-text-100">{title}</div>
<div className="pl-2 text-sm font-medium text-custom-text-300">{count || 0}</div>
{!isNil(count) && <div className="pl-2 text-sm font-medium text-custom-text-300">{count || 0}</div>}
</div>
{!disableIssueCreation &&
@@ -37,6 +37,7 @@ import { IssueBlocksList } from "./blocks-list";
import { HeaderGroupByCard } from "./headers/group-by-card";
import { TRenderQuickActions } from "./list-view-types";
import { ListQuickAddIssueForm } from "./quick-add-issue-form";
import isNil from "lodash/isNil";
interface Props {
groupIssueIds: string[] | undefined;
@@ -98,7 +99,7 @@ export const ListGroup = observer((props: Props) => {
const [intersectionElement, setIntersectionElement] = useState<HTMLDivElement | null>(null);
const groupIssueCount = getGroupIssueCount(group.id, undefined, false) ?? 0;
const groupIssueCount = getGroupIssueCount(group.id, undefined, false);
const nextPageResults = getPaginationData(group.id, undefined)?.nextPageResults;
const isPaginating = !!getIssueLoader(group.id);
@@ -106,7 +107,7 @@ export const ListGroup = observer((props: Props) => {
const shouldLoadMore =
nextPageResults === undefined && groupIssueCount !== undefined && groupIssueIds
? groupIssueIds.length < groupIssueCount
? isNil(groupIssueCount) || groupIssueIds.length < groupIssueCount
: !!nextPageResults;
const loadMore = isPaginating ? (
@@ -39,6 +39,7 @@ import { IMemberRootStore } from "@/store/member";
import { IModuleStore } from "@/store/module.store";
import { IProjectStore } from "@/store/project/project.store";
import { IStateStore } from "@/store/state.store";
import { ALL_ISSUES } from "@plane/constants";
export const HIGHLIGHT_CLASS = "highlight";
export const HIGHLIGHT_WITH_LINE = "highlight-with-line";
@@ -92,8 +93,10 @@ export const getGroupByColumns = (
case "created_by":
return getCreatedByColumns(member) as any;
default:
if (includeNone) return [{ id: `All Issues`, name: `All Issues`, payload: {}, icon: undefined }];
if (includeNone) return [{ id: ALL_ISSUES, name: ALL_ISSUES, payload: {}, icon: undefined }];
}
return;
};
const getProjectColumns = (project: IProjectStore): IGroupByColumn[] | undefined => {
@@ -59,37 +59,37 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
workspaceSlug && projectId ? () => fetchUserProjectInfo(workspaceSlug.toString(), projectId.toString()) : null
);
// fetching project labels
useSWR(
const { isLoading: isLabelsLoading } = useSWR(
workspaceSlug && projectId ? `PROJECT_LABELS_${workspaceSlug}_${projectId}` : null,
workspaceSlug && projectId ? () => fetchProjectLabels(workspaceSlug.toString(), projectId.toString()) : null,
{ revalidateIfStale: false, revalidateOnFocus: false }
);
// fetching project members
useSWR(
const { isLoading: isMembersLoading } = useSWR(
workspaceSlug && projectId ? `PROJECT_MEMBERS_${workspaceSlug}_${projectId}` : null,
workspaceSlug && projectId ? () => fetchProjectMembers(workspaceSlug.toString(), projectId.toString()) : null,
{ revalidateIfStale: false, revalidateOnFocus: false }
);
// fetching project states
useSWR(
const { isLoading: isStateLoading } = useSWR(
workspaceSlug && projectId ? `PROJECT_STATES_${workspaceSlug}_${projectId}` : null,
workspaceSlug && projectId ? () => fetchProjectStates(workspaceSlug.toString(), projectId.toString()) : null,
{ revalidateIfStale: false, revalidateOnFocus: false }
);
// fetching project estimates
useSWR(
const { isLoading: isEstimatesLoading } = useSWR(
workspaceSlug && projectId ? `PROJECT_ESTIMATES_${workspaceSlug}_${projectId}` : null,
workspaceSlug && projectId ? () => getProjectEstimates(workspaceSlug.toString(), projectId.toString()) : null,
{ revalidateIfStale: false, revalidateOnFocus: false }
);
// fetching project cycles
useSWR(
const { isLoading: isCyclesLoading } = useSWR(
workspaceSlug && projectId ? `PROJECT_ALL_CYCLES_${workspaceSlug}_${projectId}` : null,
workspaceSlug && projectId ? () => fetchAllCycles(workspaceSlug.toString(), projectId.toString()) : null,
{ revalidateIfStale: false, revalidateOnFocus: false }
);
// fetching project modules
useSWR(
const { isLoading: isModulesLoading } = useSWR(
workspaceSlug && projectId ? `PROJECT_MODULES_${workspaceSlug}_${projectId}` : null,
workspaceSlug && projectId ? () => fetchModules(workspaceSlug.toString(), projectId.toString()) : null,
{ revalidateIfStale: false, revalidateOnFocus: false }
@@ -102,8 +102,11 @@ export const ProjectAuthWrapper: FC<IProjectAuthWrapper> = observer((props) => {
);
const projectExists = projectId ? getProjectById(projectId.toString()) : null;
const isLoading =
isLabelsLoading || isMembersLoading || isStateLoading || isEstimatesLoading || isCyclesLoading || isModulesLoading;
// check if the project member apis is loading
if (!projectMemberInfo && projectId && hasPermissionToProject[projectId.toString()] === null)
if ((!projectMemberInfo && projectId && hasPermissionToProject[projectId.toString()] === null) || isLoading)
return (
<div className="grid h-screen place-items-center bg-custom-background-100 p-4">
<div className="flex flex-col items-center gap-3 text-center">
+1 -6
View File
@@ -119,12 +119,7 @@ export class CycleIssuesFilter extends IssueFilterHelperStore implements ICycleI
groupId: string | undefined,
subGroupId: string | undefined
) => {
let filterParams = this.getAppliedFilters(cycleId);
if (!filterParams) {
filterParams = {};
}
filterParams["cycle"] = cycleId;
const filterParams = this.getAppliedFilters(cycleId);
const paginationParams = this.getPaginationParams(filterParams, options, cursor, groupId, subGroupId);
return paginationParams;
+2 -2
View File
@@ -164,7 +164,7 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
// get params from pagination options
const params = this.issueFilterStore?.getFilterParams(options, cycleId, undefined, undefined, undefined);
// call the fetch issues API with the params
const response = await this.issueService.getIssues(workspaceSlug, projectId, params, {
const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params, {
signal: this.controller.signal,
});
@@ -212,7 +212,7 @@ export class CycleIssues extends BaseIssuesStore implements ICycleIssues {
subGroupId
);
// call the fetch issues API with the params for next page in issues
const response = await this.issueService.getIssues(workspaceSlug, projectId, cycleId, params);
const response = await this.cycleService.getCycleIssues(workspaceSlug, projectId, cycleId, params);
// after the next page of issues are fetched, call the base method to process the response
this.onfetchNexIssues(response, groupId, subGroupId);
@@ -44,6 +44,7 @@ import {
getSubGroupIssueKeyActions,
} from "./base-issues-utils";
import { IBaseIssueFilterStore } from "./issue-filter-helper.store";
import { getGroupByColumns } from "@/components/issues/issue-layouts/utils";
// constants
// helpers
// services
@@ -419,6 +420,56 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
}
);
getGroups = (isWorkspaceLevel = false) => {
if (!this.groupBy || this.groupBy === "target_date") return;
const {
projectRoot,
cycle,
module: moduleInfo,
label,
state: projectState,
memberRoot,
} = this.rootIssueStore.rootStore;
return getGroupByColumns(
this.groupBy,
projectRoot.project,
cycle,
moduleInfo,
label,
projectState,
memberRoot,
false,
isWorkspaceLevel
);
};
getSubGroups = (isWorkspaceLevel = false) => {
if (!this.subGroupBy || this.subGroupBy === "target_date") return;
const {
projectRoot,
cycle,
module: moduleInfo,
label,
state: projectState,
memberRoot,
} = this.rootIssueStore.rootStore;
return getGroupByColumns(
this.subGroupBy,
projectRoot.project,
cycle,
moduleInfo,
label,
projectState,
memberRoot,
false,
isWorkspaceLevel
);
};
/**
* Gets the next page cursor based on number of issues currently available
* @param groupId groupId for the cursor
@@ -451,9 +502,8 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
onfetchIssues(
issuesResponse: TIssuesResponse,
options: IssuePaginationOptions,
workspaceSlug: string,
projectId?: string,
id?: string
groupId?: string,
subGroupId?: string
) {
// Process the Issue Response to get the following data from it
const { issueList, groupedIssues, groupedIssueCount } = this.processIssueResponse(issuesResponse);
@@ -463,15 +513,12 @@ export abstract class BaseIssuesStore implements IBaseIssuesStore {
// Update all the GroupIds to this Store's groupedIssueIds and update Individual group issue counts
runInAction(() => {
this.updateGroupedIssueIds(groupedIssues, groupedIssueCount);
this.loader[getGroupKey()] = undefined;
this.updateGroupedIssueIds(groupedIssues, groupedIssueCount, groupId, subGroupId);
this.loader[getGroupKey(groupId, subGroupId)] = undefined;
});
// fetch parent stats if required, to be handled in the Implemented class
this.fetchParentStats(workspaceSlug, projectId, id);
// store Pagination options for next subsequent calls and data like next cursor etc
this.storePreviousPaginationValues(issuesResponse, options);
this.storePreviousPaginationValues(issuesResponse, options, groupId, subGroupId);
}
/**
@@ -282,7 +282,7 @@ export class IssueFilterHelperStore implements IIssueFilterHelperStore {
subGroupId?: string
) {
// if cursor exists, use the cursor. If it doesn't exist construct the cursor based on per page count
const pageCursor = cursor ? cursor : groupId ? `${options.perPageCount}:1:0` : `${options.perPageCount}:0:0`;
const pageCursor = cursor ? cursor : groupId ? `${options.perPageCount}:0:0` : `${options.perPageCount}:0:0`;
// pagination params
const paginationParams: Partial<Record<TIssueParams, string | boolean>> = {
+1 -6
View File
@@ -119,12 +119,7 @@ export class ModuleIssuesFilter extends IssueFilterHelperStore implements IModul
groupId: string | undefined,
subGroupId: string | undefined
) => {
let filterParams = this.getAppliedFilters(moduleId);
if (!filterParams) {
filterParams = {};
}
filterParams["module"] = moduleId;
const filterParams = this.getAppliedFilters(moduleId);
const paginationParams = this.getPaginationParams(filterParams, options, cursor, groupId, subGroupId);
return paginationParams;
+2 -2
View File
@@ -118,7 +118,7 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
// get params from pagination options
const params = this.issueFilterStore?.getFilterParams(options, moduleId, undefined, undefined, undefined);
// call the fetch issues API with the params
const response = await this.issueService.getIssues(workspaceSlug, projectId, params, {
const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params, {
signal: this.controller.signal,
});
@@ -166,7 +166,7 @@ export class ModuleIssues extends BaseIssuesStore implements IModuleIssues {
subGroupId
);
// call the fetch issues API with the params for next page in issues
const response = await this.issueService.getIssues(workspaceSlug, projectId, params);
const response = await this.moduleService.getModuleIssues(workspaceSlug, projectId, moduleId, params);
// after the next page of issues are fetched, call the base method to process the response
this.onfetchNexIssues(response, groupId, subGroupId);
+59 -6
View File
@@ -86,21 +86,74 @@ export class ProjectIssues extends BaseIssuesStore implements IProjectIssues {
isExistingPaginationOptions: boolean = false
) => {
try {
// set loader and clear store
runInAction(() => {
this.setLoader(loadType);
});
const groups = this.getGroups();
const subGroups = this.getSubGroups();
this.clear(!isExistingPaginationOptions);
if (!groups || (!groups && !subGroups))
return await this.fetchParallelIssues(workspaceSlug, projectId, loadType, options);
const promises = [];
for (const group of groups) {
if (!subGroups) {
promises.push(this.fetchParallelIssues(workspaceSlug, projectId, loadType, options, group.id));
continue;
}
for (const subGroup of subGroups) {
promises.push(this.fetchParallelIssues(workspaceSlug, projectId, loadType, options, group.id, subGroup.id));
}
}
await Promise.all(promises);
// fetch parent stats if required, to be handled in the Implemented class
this.fetchParentStats(workspaceSlug, projectId);
} catch (error) {
// set loader to undefined if errored out
this.setLoader(undefined);
throw error;
}
};
/**
* This method is called to fetch the first issues of pagination
* @param workspaceSlug
* @param projectId
* @param loadType
* @param options
* @returns
*/
fetchParallelIssues = async (
workspaceSlug: string,
projectId: string,
loadType: TLoader = "init-loader",
options: IssuePaginationOptions,
groupId?: string,
subGroupId?: string
) => {
try {
// set loader and clear store
runInAction(() => {
// set Loader
if (groupId || subGroupId) {
this.setLoader("pagination", groupId, subGroupId);
} else {
this.setLoader(loadType);
}
});
// get params from pagination options
const params = this.issueFilterStore?.getFilterParams(options, projectId, undefined, undefined, undefined);
const params = this.issueFilterStore?.getFilterParams(options, projectId, undefined, groupId, subGroupId);
// call the fetch issues API with the params
const response = await this.issueService.getIssues(workspaceSlug, projectId, params, {
signal: this.controller.signal,
});
// after fetching issues, call the base method to process the response further
this.onfetchIssues(response, options, workspaceSlug, projectId);
this.onfetchIssues(response, options, groupId, subGroupId);
return response;
} catch (error) {
// set loader to undefined if errored out