dev: workspace estimates

This commit is contained in:
NarayanBavisetti 2023-12-26 15:51:41 +05:30
parent 1a2186cca4
commit a8eebbae1f
7 changed files with 208 additions and 70 deletions

View File

@ -1,13 +1,34 @@
from django.urls import path
from plane.app.views import (
ProjectEstimatePointEndpoint,
BulkEstimatePointEndpoint,
WorkspaceEstimateEndpoint,
)
urlpatterns = [
path(
"workspaces/<str:slug>/estimates/",
WorkspaceEstimateEndpoint.as_view(
{
"get": "list",
"post": "create",
}
),
name="workspace-estimate-points",
),
path(
"workspaces/<str:slug>/estimates/<uuid:estimate_id>/",
WorkspaceEstimateEndpoint.as_view(
{
"get": "retrieve",
"patch": "partial_update",
"delete": "destroy",
}
),
name="workspace-estimate-points",
),
path(
"workspaces/<str:slug>/projects/<uuid:project_id>/project-estimates/",
ProjectEstimatePointEndpoint.as_view(),

View File

@ -141,6 +141,7 @@ from .external import GPTIntegrationEndpoint, ReleaseNotesEndpoint, UnsplashEndp
from .estimate import (
ProjectEstimatePointEndpoint,
BulkEstimatePointEndpoint,
WorkspaceEstimateEndpoint,
)
from .inbox import InboxViewSet, InboxIssueViewSet

View File

@ -4,13 +4,158 @@ 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.app.permissions import ProjectEntityPermission, WorkspaceEntityPermission
from plane.db.models import Project, Estimate, EstimatePoint, Workspace
from plane.app.serializers import (
EstimateSerializer,
EstimatePointSerializer,
EstimateReadSerializer,
)
from django.db.models import Q
class WorkspaceEstimateEndpoint(BaseViewSet):
permission_classes = [
WorkspaceEntityPermission,
]
model = Estimate
serializer_class = EstimateSerializer
def list(self, request, slug):
estimates = Estimate.objects.filter(
workspace__slug=slug, project_id__isnull=True
).prefetch_related("points").select_related("workspace")
serializer = EstimateReadSerializer(estimates, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
def create(self, request, slug):
if not request.data.get("estimate", False):
return Response(
{"error": "Estimate is required"},
status=status.HTTP_400_BAD_REQUEST,
)
estimate_points = request.data.get("estimate_points", [])
if not len(estimate_points) or len(estimate_points) > 8:
return Response(
{"error": "Estimate points are required"},
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
)
workspace = Workspace.objects.get(slug=slug)
estimate = estimate_serializer.save(workspace_id=workspace.id)
estimate_points = EstimatePoint.objects.bulk_create(
[
EstimatePoint(
estimate=estimate,
key=estimate_point.get("key", 0),
value=estimate_point.get("value", ""),
description=estimate_point.get("description", ""),
workspace_id=estimate.workspace_id,
created_by=request.user,
updated_by=request.user,
)
for estimate_point in estimate_points
],
batch_size=10,
ignore_conflicts=True,
)
estimate_point_serializer = EstimatePointSerializer(
estimate_points, many=True
)
return Response(
{
"estimate": estimate_serializer.data,
"estimate_points": estimate_point_serializer.data,
},
status=status.HTTP_200_OK,
)
def retrieve(self, request, slug, estimate_id):
estimate = Estimate.objects.get(
pk=estimate_id, workspace__slug=slug
)
serializer = EstimateReadSerializer(estimate)
return Response(
serializer.data,
status=status.HTTP_200_OK,
)
def partial_update(self, request, slug, 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(
{"error": "Estimate points are required"},
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_points_data = request.data.get("estimate_points", [])
estimate_points = EstimatePoint.objects.filter(
pk__in=[
estimate_point.get("id") for estimate_point in estimate_points_data
],
workspace__slug=slug,
estimate_id=estimate_id,
)
updated_estimate_points = []
for estimate_point in estimate_points:
# Find the data for that estimate point
estimate_point_data = [
point
for point in estimate_points_data
if point.get("id") == str(estimate_point.id)
]
if len(estimate_point_data):
estimate_point.value = estimate_point_data[0].get(
"value", estimate_point.value
)
updated_estimate_points.append(estimate_point)
EstimatePoint.objects.bulk_update(
updated_estimate_points, ["value"], batch_size=10,
)
estimate_point_serializer = EstimatePointSerializer(estimate_points, many=True)
return Response(
{
"estimate": estimate_serializer.data,
"estimate_points": estimate_point_serializer.data,
},
status=status.HTTP_200_OK,
)
def destroy(self, request, slug, estimate_id):
estimate = Estimate.objects.get(
pk=estimate_id, workspace__slug=slug
)
estimate.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
class ProjectEstimatePointEndpoint(BaseAPIView):
@ -39,6 +184,14 @@ class BulkEstimatePointEndpoint(BaseViewSet):
serializer_class = EstimateSerializer
def list(self, request, slug, project_id):
workspace = request.GET.get("workspace", False)
if workspace:
estimates = Estimate.objects.filter(
Q(project_id=project_id) | Q(project_id__isnull=True), workspace__slug=slug,
).prefetch_related("points").select_related("workspace")
serializer = EstimateReadSerializer(estimates, many=True)
return Response(serializer.data, status=status.HTTP_200_OK)
estimates = Estimate.objects.filter(
workspace__slug=slug, project_id=project_id
).prefetch_related("points").select_related("workspace", "project")

View File

@ -1,7 +1,7 @@
# Generated by Django 4.2.7 on 2023-12-20 14:33
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
@ -10,69 +10,14 @@ class Migration(migrations.Migration):
]
operations = [
migrations.AddField(
model_name='cycle',
name='external_id',
field=models.CharField(blank=True, max_length=255, null=True),
migrations.AlterField(
model_name='estimate',
name='project',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='project_%(class)s', to='db.project'),
),
migrations.AddField(
model_name='cycle',
name='external_source',
field=models.CharField(blank=True, null=True),
),
migrations.AddField(
model_name='importer',
name='reason',
field=models.TextField(blank=True),
),
migrations.AddField(
model_name='issue',
name='external_id',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='issue',
name='external_source',
field=models.CharField(blank=True, null=True),
),
migrations.AddField(
model_name='issuecomment',
name='external_id',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='issuecomment',
name='external_source',
field=models.CharField(blank=True, null=True),
),
migrations.AddField(
model_name='label',
name='external_id',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='label',
name='external_source',
field=models.CharField(blank=True, null=True),
),
migrations.AddField(
model_name='module',
name='external_id',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='module',
name='external_source',
field=models.CharField(blank=True, null=True),
),
migrations.AddField(
model_name='state',
name='external_id',
field=models.CharField(blank=True, max_length=255, null=True),
),
migrations.AddField(
model_name='state',
name='external_source',
field=models.CharField(blank=True, null=True),
migrations.AlterField(
model_name='estimatepoint',
name='project',
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='project_%(class)s', to='db.project'),
),
]

View File

@ -9,6 +9,7 @@ from .workspace import (
WorkspaceMemberInvite,
TeamMember,
WorkspaceTheme,
WorkspaceBaseModel,
)
from .project import (

View File

@ -3,10 +3,10 @@ from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
# Module imports
from . import ProjectBaseModel
from . import WorkspaceBaseModel
class Estimate(ProjectBaseModel):
class Estimate(WorkspaceBaseModel):
name = models.CharField(max_length=255)
description = models.TextField(verbose_name="Estimate Description", blank=True)
@ -22,7 +22,7 @@ class Estimate(ProjectBaseModel):
ordering = ("name",)
class EstimatePoint(ProjectBaseModel):
class EstimatePoint(WorkspaceBaseModel):
estimate = models.ForeignKey(
"db.Estimate",
on_delete=models.CASCADE,

View File

@ -103,6 +103,23 @@ class Workspace(BaseModel):
ordering = ("-created_at",)
class WorkspaceBaseModel(BaseModel):
workspace = models.ForeignKey(
"db.Workspace", models.CASCADE, related_name="workspace_%(class)s"
)
project = models.ForeignKey(
"db.Project", models.CASCADE, related_name="project_%(class)s", null=True
)
class Meta:
abstract = True
def save(self, *args, **kwargs):
if self.project:
self.workspace = self.project.workspace
super(WorkspaceBaseModel, self).save(*args, **kwargs)
class WorkspaceMember(BaseModel):
workspace = models.ForeignKey(
"db.Workspace", on_delete=models.CASCADE, related_name="workspace_member"