From c811ecaa15488891268e2be76df4c6df12d6d439 Mon Sep 17 00:00:00 2001 From: NarayanBavisetti Date: Fri, 24 May 2024 12:09:36 +0530 Subject: [PATCH] chore: delete an estimate point --- apiserver/plane/app/urls/estimate.py | 6 + apiserver/plane/app/views/__init__.py | 1 + apiserver/plane/app/views/estimate/base.py | 104 ++++++++++++------ .../db/migrations/0066_auto_20240522_0957.py | 24 ++++ apiserver/plane/db/models/issue.py | 7 ++ 5 files changed, 107 insertions(+), 35 deletions(-) create mode 100644 apiserver/plane/db/migrations/0066_auto_20240522_0957.py diff --git a/apiserver/plane/app/urls/estimate.py b/apiserver/plane/app/urls/estimate.py index d8571ff0c..d5a87c413 100644 --- a/apiserver/plane/app/urls/estimate.py +++ b/apiserver/plane/app/urls/estimate.py @@ -4,6 +4,7 @@ from django.urls import path from plane.app.views import ( ProjectEstimatePointEndpoint, BulkEstimatePointEndpoint, + DeleteEstimatePoint, ) @@ -34,4 +35,9 @@ urlpatterns = [ ), name="bulk-create-estimate-points", ), + path( + "workspaces//projects//estimates//estimate-point//", + DeleteEstimatePoint.as_view({"patch": "partial_update"}), + name="bulk-create-estimate-points", + ), ] diff --git a/apiserver/plane/app/views/__init__.py b/apiserver/plane/app/views/__init__.py index bf765e719..07a4e6e9e 100644 --- a/apiserver/plane/app/views/__init__.py +++ b/apiserver/plane/app/views/__init__.py @@ -189,6 +189,7 @@ from .external.base import ( from .estimate.base import ( ProjectEstimatePointEndpoint, BulkEstimatePointEndpoint, + DeleteEstimatePoint, ) from .inbox.base import InboxViewSet, InboxIssueViewSet diff --git a/apiserver/plane/app/views/estimate/base.py b/apiserver/plane/app/views/estimate/base.py index 7ac3035a9..bfe4e91df 100644 --- a/apiserver/plane/app/views/estimate/base.py +++ b/apiserver/plane/app/views/estimate/base.py @@ -1,3 +1,6 @@ +import random +import string + # Third party imports from rest_framework.response import Response from rest_framework import status @@ -5,7 +8,7 @@ from rest_framework import status # Module imports from ..base import BaseViewSet, BaseAPIView from plane.app.permissions import ProjectEntityPermission -from plane.db.models import Project, Estimate, EstimatePoint +from plane.db.models import Project, Estimate, EstimatePoint, Issue from plane.app.serializers import ( EstimateSerializer, EstimatePointSerializer, @@ -13,6 +16,12 @@ from plane.app.serializers import ( ) from plane.utils.cache import invalidate_cache + +def generate_random_name(length=10): + letters = string.ascii_lowercase + return "".join(random.choice(letters) for i in range(length)) + + class ProjectEstimatePointEndpoint(BaseAPIView): permission_classes = [ ProjectEntityPermission, @@ -49,13 +58,14 @@ class BulkEstimatePointEndpoint(BaseViewSet): serializer = EstimateReadSerializer(estimates, many=True) return Response(serializer.data, status=status.HTTP_200_OK) - @invalidate_cache(path="/api/workspaces/:slug/estimates/", url_params=True, user=False) + @invalidate_cache( + path="/api/workspaces/:slug/estimates/", url_params=True, user=False + ) def create(self, request, slug, project_id): - if not request.data.get("estimate", False): - return Response( - {"error": "Estimate is required"}, - status=status.HTTP_400_BAD_REQUEST, - ) + estimate_name = generate_random_name() + estimate = Estimate.objects.create( + name=estimate_name, project_id=project_id + ) estimate_points = request.data.get("estimate_points", []) @@ -67,14 +77,6 @@ class BulkEstimatePointEndpoint(BaseViewSet): serializer.errors, status=status.HTTP_400_BAD_REQUEST ) - estimate_serializer = EstimateSerializer( - data=request.data.get("estimate") - ) - if not estimate_serializer.is_valid(): - return Response( - estimate_serializer.errors, status=status.HTTP_400_BAD_REQUEST - ) - estimate = estimate_serializer.save(project_id=project_id) estimate_points = EstimatePoint.objects.bulk_create( [ EstimatePoint( @@ -99,7 +101,6 @@ class BulkEstimatePointEndpoint(BaseViewSet): return Response( { - "estimate": estimate_serializer.data, "estimate_points": estimate_point_serializer.data, }, status=status.HTTP_200_OK, @@ -115,13 +116,10 @@ class BulkEstimatePointEndpoint(BaseViewSet): status=status.HTTP_200_OK, ) - @invalidate_cache(path="/api/workspaces/:slug/estimates/", url_params=True, user=False) + @invalidate_cache( + path="/api/workspaces/:slug/estimates/", url_params=True, user=False + ) def partial_update(self, request, slug, project_id, estimate_id): - if not request.data.get("estimate", False): - return Response( - {"error": "Estimate is required"}, - status=status.HTTP_400_BAD_REQUEST, - ) if not len(request.data.get("estimate_points", [])): return Response( @@ -129,17 +127,7 @@ class BulkEstimatePointEndpoint(BaseViewSet): status=status.HTTP_400_BAD_REQUEST, ) - estimate = Estimate.objects.get(pk=estimate_id) - - estimate_serializer = EstimateSerializer( - estimate, data=request.data.get("estimate"), partial=True - ) - if not estimate_serializer.is_valid(): - return Response( - estimate_serializer.errors, status=status.HTTP_400_BAD_REQUEST - ) - - estimate = estimate_serializer.save() + _ = Estimate.objects.get(pk=estimate_id) estimate_points_data = request.data.get("estimate_points", []) @@ -178,16 +166,62 @@ class BulkEstimatePointEndpoint(BaseViewSet): ) return Response( { - "estimate": estimate_serializer.data, "estimate_points": estimate_point_serializer.data, }, status=status.HTTP_200_OK, ) - @invalidate_cache(path="/api/workspaces/:slug/estimates/", url_params=True, user=False) + @invalidate_cache( + path="/api/workspaces/:slug/estimates/", url_params=True, user=False + ) def destroy(self, request, slug, project_id, estimate_id): estimate = Estimate.objects.get( pk=estimate_id, workspace__slug=slug, project_id=project_id ) estimate.delete() return Response(status=status.HTTP_204_NO_CONTENT) + + +class DeleteEstimatePoint(BaseViewSet): + permission_classes = [ + ProjectEntityPermission, + ] + + def partial_update( + self, request, slug, project_id, estimate_id, estimate_point_id + ): + new_estimate_id = request.data.get("new_estimate_id", None) + estimate_points = EstimatePoint.objects.filter( + estimate_id=estimate_id, + project_id=project_id, + workspace__slug=slug, + ) + # update all the issues with the new estimate + if new_estimate_id: + _ = Issue.objects.filter( + project_id=project_id, + workspace__slug=slug, + estimate_id=estimate_point_id, + ).update(estimate_id=new_estimate_id) + + # delete the estimate point + old_estimate_point = EstimatePoint.objects.filter( + pk=estimate_point_id + ).first() + + # rearrange the estimate points + updated_estimate_points = [] + for estimate_point in estimate_points: + if estimate_point.key > old_estimate_point.key: + estimate_point.key -= 1 + updated_estimate_points.append(estimate_point) + + EstimatePoint.objects.bulk_update( + updated_estimate_points, + ["key"], + batch_size=10, + ) + + old_estimate_point.delete() + + return Response(status=status.HTTP_204_NO_CONTENT) diff --git a/apiserver/plane/db/migrations/0066_auto_20240522_0957.py b/apiserver/plane/db/migrations/0066_auto_20240522_0957.py new file mode 100644 index 000000000..6681b6891 --- /dev/null +++ b/apiserver/plane/db/migrations/0066_auto_20240522_0957.py @@ -0,0 +1,24 @@ +# Generated by Django 4.2.7 on 2024-05-22 09:57 + +from django.db import migrations, models +import django.db.models.deletion + +class Migration(migrations.Migration): + + dependencies = [ + ("db", "0065_auto_20240415_0937"), + ] + + operations = [ + migrations.AddField( + model_name="issue", + name="estimate", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="issue_estimate", + to="db.estimatepoint", + ), + ), + ] diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index 7a17853c3..41aacf194 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -124,6 +124,13 @@ class Issue(ProjectBaseModel): null=True, blank=True, ) + estimate = models.ForeignKey( + "db.Estimate", + on_delete=models.SET_NULL, + related_name="issue_estimate", + null=True, + blank=True, + ) name = models.CharField(max_length=255, verbose_name="Issue Name") description = models.JSONField(blank=True, default=dict) description_html = models.TextField(blank=True, default="

")