From 0c6fc9e20063c097d6c150257c448eb7ad07a29e Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 31 Jul 2023 12:13:55 +0530 Subject: [PATCH 1/4] feat: user project sorting --- apiserver/plane/api/views/project.py | 8 +++++++- .../plane/db/migrations/0039_auto_20230723_2203.py | 10 +++++----- apiserver/plane/db/models/project.py | 3 ++- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index d8da4f7dd3a..478925d3a84 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -5,7 +5,7 @@ # Django imports from django.core.exceptions import ValidationError from django.db import IntegrityError -from django.db.models import Q, Exists, OuterRef, Func, F +from django.db.models import Q, Exists, OuterRef, Func, F, Subquery from django.core.validators import validate_email from django.conf import settings @@ -120,9 +120,15 @@ def list(self, request, slug): project_id=OuterRef("pk"), workspace__slug=self.kwargs.get("slug"), ) + sort_order = ProjectMember.objects.filter( + user=self.request.user, + project_id=OuterRef("pk"), + workspace__slug=self.kwargs.get("slug"), + ).values("sort_order") projects = ( self.get_queryset() .annotate(is_favorite=Exists(subquery)) + .annotate(sort_order=Subquery(sort_order)) .order_by("sort_order", "name") .annotate( total_members=ProjectMember.objects.filter( diff --git a/apiserver/plane/db/migrations/0039_auto_20230723_2203.py b/apiserver/plane/db/migrations/0039_auto_20230723_2203.py index 78b77521cd5..3b6b89c9b79 100644 --- a/apiserver/plane/db/migrations/0039_auto_20230723_2203.py +++ b/apiserver/plane/db/migrations/0039_auto_20230723_2203.py @@ -58,16 +58,16 @@ def update_workspace_member_props(apps, schema_editor): Model.objects.bulk_update(updated_workspace_member, ["view_props"], batch_size=100) -def update_project_sort_order(apps, schema_editor): - Model = apps.get_model("db", "Project") +def update_project_member_sort_order(apps, schema_editor): + Model = apps.get_model("db", "ProjectMember") - updated_projects = [] + updated_project_members = [] for obj in Model.objects.all(): obj.sort_order = random.randint(1, 65536) - updated_projects.append(obj) + updated_project_members.append(obj) - Model.objects.bulk_update(updated_projects, ["sort_order"], batch_size=100) + Model.objects.bulk_update(updated_project_members, ["sort_order"], batch_size=100) class Migration(migrations.Migration): diff --git a/apiserver/plane/db/models/project.py b/apiserver/plane/db/models/project.py index f95a8c2f03a..91efb5de5e6 100644 --- a/apiserver/plane/db/models/project.py +++ b/apiserver/plane/db/models/project.py @@ -91,7 +91,6 @@ class Project(BaseModel): default_state = models.ForeignKey( "db.State", on_delete=models.SET_NULL, null=True, related_name="default_state" ) - sort_order = models.FloatField(default=65535) def __str__(self): """Return name of the project""" @@ -156,6 +155,8 @@ class ProjectMember(ProjectBaseModel): view_props = models.JSONField(default=get_default_props) default_props = models.JSONField(default=get_default_props) preferences = models.JSONField(default=get_default_preferences) + sort_order = models.FloatField(default=65535) + class Meta: unique_together = ["project", "member"] From d8244533eba6dc724bf5d76ab062f4b520be9555 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 31 Jul 2023 12:24:34 +0530 Subject: [PATCH 2/4] dev: migration typo fix and query member fix --- apiserver/plane/api/views/project.py | 6 +++--- apiserver/plane/db/migrations/0039_auto_20230723_2203.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index 478925d3a84..5d48c98418a 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -120,15 +120,15 @@ def list(self, request, slug): project_id=OuterRef("pk"), workspace__slug=self.kwargs.get("slug"), ) - sort_order = ProjectMember.objects.filter( - user=self.request.user, + sort_order_query = ProjectMember.objects.filter( + member=request.user, project_id=OuterRef("pk"), workspace__slug=self.kwargs.get("slug"), ).values("sort_order") projects = ( self.get_queryset() .annotate(is_favorite=Exists(subquery)) - .annotate(sort_order=Subquery(sort_order)) + .annotate(sort_order=Subquery(sort_order_query)) .order_by("sort_order", "name") .annotate( total_members=ProjectMember.objects.filter( diff --git a/apiserver/plane/db/migrations/0039_auto_20230723_2203.py b/apiserver/plane/db/migrations/0039_auto_20230723_2203.py index 3b6b89c9b79..8f8700b5d48 100644 --- a/apiserver/plane/db/migrations/0039_auto_20230723_2203.py +++ b/apiserver/plane/db/migrations/0039_auto_20230723_2203.py @@ -93,5 +93,5 @@ class Migration(migrations.Migration): name='sort_order', field=models.FloatField(default=65535), ), - migrations.RunPython(update_project_sort_order), + migrations.RunPython(update_project_member_sort_order), ] From 351dbcb2cfd212bff9fb08fd928af1e54886dcac Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 31 Jul 2023 12:29:04 +0530 Subject: [PATCH 3/4] feat: project member sort order update --- apiserver/plane/api/serializers/project.py | 1 + apiserver/plane/api/views/project.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/apiserver/plane/api/serializers/project.py b/apiserver/plane/api/serializers/project.py index 641edb07ca4..fa97c5a6ddf 100644 --- a/apiserver/plane/api/serializers/project.py +++ b/apiserver/plane/api/serializers/project.py @@ -93,6 +93,7 @@ class ProjectDetailSerializer(BaseSerializer): total_cycles = serializers.IntegerField(read_only=True) total_modules = serializers.IntegerField(read_only=True) is_member = serializers.BooleanField(read_only=True) + sort_order = serializers.FloatField(read_only=True) class Meta: model = Project diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index 5d48c98418a..3a1e2a73d19 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -851,12 +851,14 @@ def post(self, request, slug, project_id): view_props = project_member.view_props default_props = project_member.default_props preferences = project_member.preferences + sort_order = project_member.sort_order project_member.view_props = request.data.get("view_props", view_props) project_member.default_props = request.data.get( "default_props", default_props ) project_member.preferences = request.data.get("preferences", preferences) + project_member.sort_order = request.data.get("sort_order", sort_order) project_member.save() From d96857c81b3e89488efbd11c532d679399620e73 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 31 Jul 2023 17:02:46 +0530 Subject: [PATCH 4/4] fix: project sorting per members --- apiserver/plane/api/views/project.py | 19 ++++++++++++++----- apiserver/plane/db/models/project.py | 12 ++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index 3a1e2a73d19..26064d33152 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -5,7 +5,7 @@ # Django imports from django.core.exceptions import ValidationError from django.db import IntegrityError -from django.db.models import Q, Exists, OuterRef, Func, F, Subquery +from django.db.models import Q, Exists, OuterRef, Func, F, Min, Subquery from django.core.validators import validate_email from django.conf import settings @@ -598,17 +598,26 @@ def post(self, request, slug, project_id): {"error": "Atleast one member is required"}, status=status.HTTP_400_BAD_REQUEST, ) + bulk_project_members = [] - project_members = ProjectMember.objects.bulk_create( - [ + project_members = ProjectMember.objects.filter( + workspace=self.workspace, member_id__in=[member.get("member_id") for member in members] + ).values("member_id").annotate(sort_order_min=Min("sort_order")) + + for member in members: + sort_order = [project_member.get("sort_order") for project_member in project_members] + bulk_project_members.append( ProjectMember( member_id=member.get("member_id"), role=member.get("role", 10), project_id=project_id, workspace_id=project.workspace_id, + sort_order=sort_order[0] - 10000 if len(sort_order) else 65535 ) - for member in members - ], + ) + + project_members = ProjectMember.objects.bulk_create( + bulk_project_members, batch_size=10, ignore_conflicts=True, ) diff --git a/apiserver/plane/db/models/project.py b/apiserver/plane/db/models/project.py index 91efb5de5e6..7049e3ced93 100644 --- a/apiserver/plane/db/models/project.py +++ b/apiserver/plane/db/models/project.py @@ -158,6 +158,18 @@ class ProjectMember(ProjectBaseModel): sort_order = models.FloatField(default=65535) + def save(self, *args, **kwargs): + if self._state.adding: + smallest_sort_order = ProjectMember.objects.filter( + workspace=self.workspace, member=self.member + ).aggregate(smallest=models.Min("sort_order"))["smallest"] + + # Project ordering + if smallest_sort_order is not None: + self.sort_order = smallest_sort_order - 10000 + + super(ProjectMember, self).save(*args, **kwargs) + class Meta: unique_together = ["project", "member"] verbose_name = "Project Member"