mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Merge branch 'revamp-estimates' of gurusainath:makeplane/plane into revamp-estimates
This commit is contained in:
commit
07e905f879
@ -4,6 +4,7 @@ from django.urls import path
|
|||||||
from plane.app.views import (
|
from plane.app.views import (
|
||||||
ProjectEstimatePointEndpoint,
|
ProjectEstimatePointEndpoint,
|
||||||
BulkEstimatePointEndpoint,
|
BulkEstimatePointEndpoint,
|
||||||
|
DeleteEstimatePoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -34,4 +35,9 @@ urlpatterns = [
|
|||||||
),
|
),
|
||||||
name="bulk-create-estimate-points",
|
name="bulk-create-estimate-points",
|
||||||
),
|
),
|
||||||
|
path(
|
||||||
|
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/<uuid:estimate_id>/estimate-point/<estimate_point_id>/",
|
||||||
|
DeleteEstimatePoint.as_view({"patch": "partial_update"}),
|
||||||
|
name="delete-estimate-points",
|
||||||
|
),
|
||||||
]
|
]
|
||||||
|
@ -189,6 +189,7 @@ from .external.base import (
|
|||||||
from .estimate.base import (
|
from .estimate.base import (
|
||||||
ProjectEstimatePointEndpoint,
|
ProjectEstimatePointEndpoint,
|
||||||
BulkEstimatePointEndpoint,
|
BulkEstimatePointEndpoint,
|
||||||
|
DeleteEstimatePoint,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .inbox.base import InboxViewSet, InboxIssueViewSet
|
from .inbox.base import InboxViewSet, InboxIssueViewSet
|
||||||
|
@ -1,3 +1,6 @@
|
|||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
# Third party imports
|
# Third party imports
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
@ -5,7 +8,7 @@ from rest_framework import status
|
|||||||
# Module imports
|
# Module imports
|
||||||
from ..base import BaseViewSet, BaseAPIView
|
from ..base import BaseViewSet, BaseAPIView
|
||||||
from plane.app.permissions import ProjectEntityPermission
|
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 (
|
from plane.app.serializers import (
|
||||||
EstimateSerializer,
|
EstimateSerializer,
|
||||||
EstimatePointSerializer,
|
EstimatePointSerializer,
|
||||||
@ -13,6 +16,12 @@ from plane.app.serializers import (
|
|||||||
)
|
)
|
||||||
from plane.utils.cache import invalidate_cache
|
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):
|
class ProjectEstimatePointEndpoint(BaseAPIView):
|
||||||
permission_classes = [
|
permission_classes = [
|
||||||
ProjectEntityPermission,
|
ProjectEntityPermission,
|
||||||
@ -49,13 +58,14 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||||||
serializer = EstimateReadSerializer(estimates, many=True)
|
serializer = EstimateReadSerializer(estimates, many=True)
|
||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
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):
|
def create(self, request, slug, project_id):
|
||||||
if not request.data.get("estimate", False):
|
estimate_name = generate_random_name()
|
||||||
return Response(
|
estimate = Estimate.objects.create(
|
||||||
{"error": "Estimate is required"},
|
name=estimate_name, project_id=project_id
|
||||||
status=status.HTTP_400_BAD_REQUEST,
|
)
|
||||||
)
|
|
||||||
|
|
||||||
estimate_points = request.data.get("estimate_points", [])
|
estimate_points = request.data.get("estimate_points", [])
|
||||||
|
|
||||||
@ -67,14 +77,6 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
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(
|
estimate_points = EstimatePoint.objects.bulk_create(
|
||||||
[
|
[
|
||||||
EstimatePoint(
|
EstimatePoint(
|
||||||
@ -99,7 +101,6 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||||||
|
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"estimate": estimate_serializer.data,
|
|
||||||
"estimate_points": estimate_point_serializer.data,
|
"estimate_points": estimate_point_serializer.data,
|
||||||
},
|
},
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
@ -115,13 +116,10 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||||||
status=status.HTTP_200_OK,
|
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):
|
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", [])):
|
if not len(request.data.get("estimate_points", [])):
|
||||||
return Response(
|
return Response(
|
||||||
@ -129,17 +127,7 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||||||
status=status.HTTP_400_BAD_REQUEST,
|
status=status.HTTP_400_BAD_REQUEST,
|
||||||
)
|
)
|
||||||
|
|
||||||
estimate = Estimate.objects.get(pk=estimate_id)
|
_ = 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_points_data = request.data.get("estimate_points", [])
|
estimate_points_data = request.data.get("estimate_points", [])
|
||||||
|
|
||||||
@ -178,16 +166,65 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||||||
)
|
)
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
"estimate": estimate_serializer.data,
|
|
||||||
"estimate_points": estimate_point_serializer.data,
|
"estimate_points": estimate_point_serializer.data,
|
||||||
},
|
},
|
||||||
status=status.HTTP_200_OK,
|
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):
|
def destroy(self, request, slug, project_id, estimate_id):
|
||||||
estimate = Estimate.objects.get(
|
estimate = Estimate.objects.get(
|
||||||
pk=estimate_id, workspace__slug=slug, project_id=project_id
|
pk=estimate_id, workspace__slug=slug, project_id=project_id
|
||||||
)
|
)
|
||||||
estimate.delete()
|
estimate.delete()
|
||||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
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(
|
||||||
|
EstimatePointSerializer(updated_estimate_points, many=True).data,
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
|
24
apiserver/plane/db/migrations/0066_auto_20240522_0957.py
Normal file
24
apiserver/plane/db/migrations/0066_auto_20240522_0957.py
Normal file
@ -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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -124,6 +124,13 @@ class Issue(ProjectBaseModel):
|
|||||||
null=True,
|
null=True,
|
||||||
blank=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")
|
name = models.CharField(max_length=255, verbose_name="Issue Name")
|
||||||
description = models.JSONField(blank=True, default=dict)
|
description = models.JSONField(blank=True, default=dict)
|
||||||
description_html = models.TextField(blank=True, default="<p></p>")
|
description_html = models.TextField(blank=True, default="<p></p>")
|
||||||
|
Loading…
Reference in New Issue
Block a user