feat: estimates revamp and space app refactor (#4742)
* Move code from EE to CE repo * chore: folder structure updates * Move sortabla and radio input to packages/ui * chore: updated empty and loading screens * chore: delete an estimate point * chore: estimate point response change * chore: updated create estimate and handled the build error * chore: migration fixes * chore: updated create estimate * chore: create estimate workflow update * chore: editing and deleting the existing estimate updates * chore: updating the new estinates in update modal * chore: ui changed * chore: response changes of get and post * chore: new field added in estimates * chore: individual endpoint for estimate points * chore: typo changes * chore: create estimate point * chore: integrated new endpoints * chore: update key value pair * chore: update sorting in the estimates * Add custom option in the estimate templates * chore: handled current project active estimate * chore: handle estimate update worklfow * chore: handled estimates switch * chore: handled estimate edit * chore: handled close button in estimate edit * chore: updated ceate estimare workflow * chore: updated switch estimate * chore: UI and typos * chore: resolved build error * chore: updated delete dropdown and handled the repeated values while creating and updating the estimate point * chore: handled inline errors in the estimate switch * chore: handled active and availability vadilation * chore: handled create and update components in projecr estimates * chore: added migration * Add category specific values for custom template * chore: estimate dropdown handled in issues * chore: estimate alerts * chore: updated alerts * Extract the list row actions * fix: updated and handled the estimate points * fix: upgrader ee banner * Fix issues with sortable * Fix sortable spacing issue in create estimate modal * fix: updated the issue create sorting * chore: removed radio button from ui and updated in the estimates * chore: resolved import error in packaged ui * chore: handled props in create modal * chore: removed ee files * chore: changed default analytics * chore: removed the migration file * chore: estimate point value in graph * chore: estimate point key change * chore: squashed migration (#4634) * chore: squashed migration * chore: removed instance migraion * chore: key changes * chore: issue activity back migration * dev: replaced estimate key with estimate id and replaced estimate type from number to string in issue * chore: estimate point value field * chore: estimate point activity * chore: removed the unused function * chore: resolved merge conflicts * chore: deploy board keys changed * chore: yarn lock file change * chore: resolved frontend build --------- Co-authored-by: guru_sainath <gurusainath007@gmail.com> * [WEB-1516] refactor: space app routing and layouts (#4705) * dev: change layout * chore: replace workspace slug and project id with anchor * chore: migration fixes * chore: update filtering logic * chore: endpoint changes * chore: update endpoint * chore: changed url pratterns * chore: use client side for layout and page * chore: issue vote changes * chore: project deploy board response change * refactor: publish project store and components * fix: update layout options after fetching settings * chore: remove unnecessary types * style: peek overview * refactor: components folder structure * fix: redirect from old path * chore: make the whole issue block clickable * chore: removed the migration file * chore: add server side redirection for old routes * chore: is enabled key change * chore: update types * chore: removed the migration file --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com> * Merge develop into revamp-estimates-ce * chore: removed migration file and updated the estimate system order and removed ee banner * chore: initial radio select in create estimate * chore: space key changes * Fix sortable component as the sort order was broken. * [WEB-1516] refactor: publish project modal and types (#4716) * refacotr: project publish * chore: rename service names * chore: is_deployed changed to anchor * chore: update is_deployed key --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com> * [WEB-412] chore: estimates analytics (#4730) * chore: estimate points in modules and cycle * chore: burn down chart analytics * chore: module serializer change * dev: handled y-axis estimates in analytics, implemented estimate points on modules * chore: burn down analytics * chore: state estimate point analytics * chore: updated the burn down values * Remove check mark from estimate point edit field in create estimate flow --------- Co-authored-by: guru_sainath <gurusainath007@gmail.com> Co-authored-by: Satish Gandham <satish.iitg@gmail.com> --------- Co-authored-by: Satish Gandham <satish.iitg@gmail.com> Co-authored-by: guru_sainath <gurusainath007@gmail.com> Co-authored-by: NarayanBavisetti <narayan3119@gmail.com> Co-authored-by: Bavisetti Narayan <72156168+NarayanBavisetti@users.noreply.github.com> Co-authored-by: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Co-authored-by: pushya22 <130810100+pushya22@users.noreply.github.com>
This commit is contained in:
parent
fb2b4ae303
commit
59fdd611e4
223 changed files with 6874 additions and 4658 deletions
|
|
@ -784,6 +784,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
|
|||
|
||||
def post(self, request, slug, project_id, cycle_id):
|
||||
new_cycle_id = request.data.get("new_cycle_id", False)
|
||||
plot_type = request.GET.get("plot_type", "issues")
|
||||
|
||||
if not new_cycle_id:
|
||||
return Response(
|
||||
|
|
@ -865,6 +866,7 @@ class TransferCycleIssueAPIEndpoint(BaseAPIView):
|
|||
queryset=old_cycle.first(),
|
||||
slug=slug,
|
||||
project_id=project_id,
|
||||
plot_type=plot_type,
|
||||
cycle_id=cycle_id,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ from plane.db.models import (
|
|||
IssueProperty,
|
||||
Module,
|
||||
Project,
|
||||
ProjectDeployBoard,
|
||||
DeployBoard,
|
||||
ProjectMember,
|
||||
State,
|
||||
Workspace,
|
||||
|
|
@ -99,7 +99,7 @@ class ProjectAPIEndpoint(BaseAPIView):
|
|||
)
|
||||
.annotate(
|
||||
is_deployed=Exists(
|
||||
ProjectDeployBoard.objects.filter(
|
||||
DeployBoard.objects.filter(
|
||||
project_id=OuterRef("pk"),
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ from .project import (
|
|||
ProjectIdentifierSerializer,
|
||||
ProjectLiteSerializer,
|
||||
ProjectMemberLiteSerializer,
|
||||
ProjectDeployBoardSerializer,
|
||||
DeployBoardSerializer,
|
||||
ProjectMemberAdminSerializer,
|
||||
ProjectPublicMemberSerializer,
|
||||
ProjectMemberRoleSerializer,
|
||||
|
|
|
|||
|
|
@ -11,10 +11,6 @@ from rest_framework import serializers
|
|||
|
||||
|
||||
class EstimateSerializer(BaseSerializer):
|
||||
workspace_detail = WorkspaceLiteSerializer(
|
||||
read_only=True, source="workspace"
|
||||
)
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
|
||||
class Meta:
|
||||
model = Estimate
|
||||
|
|
@ -48,10 +44,6 @@ class EstimatePointSerializer(BaseSerializer):
|
|||
|
||||
class EstimateReadSerializer(BaseSerializer):
|
||||
points = EstimatePointSerializer(read_only=True, many=True)
|
||||
workspace_detail = WorkspaceLiteSerializer(
|
||||
read_only=True, source="workspace"
|
||||
)
|
||||
project_detail = ProjectLiteSerializer(read_only=True, source="project")
|
||||
|
||||
class Meta:
|
||||
model = Estimate
|
||||
|
|
|
|||
|
|
@ -177,6 +177,8 @@ class ModuleSerializer(DynamicBaseSerializer):
|
|||
started_issues = serializers.IntegerField(read_only=True)
|
||||
unstarted_issues = serializers.IntegerField(read_only=True)
|
||||
backlog_issues = serializers.IntegerField(read_only=True)
|
||||
total_estimate_points = serializers.IntegerField(read_only=True)
|
||||
completed_estimate_points = serializers.IntegerField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Module
|
||||
|
|
@ -201,6 +203,8 @@ class ModuleSerializer(DynamicBaseSerializer):
|
|||
"external_id",
|
||||
"logo_props",
|
||||
# computed fields
|
||||
"total_estimate_points",
|
||||
"completed_estimate_points",
|
||||
"is_favorite",
|
||||
"total_issues",
|
||||
"cancelled_issues",
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from plane.db.models import (
|
|||
ProjectMember,
|
||||
ProjectMemberInvite,
|
||||
ProjectIdentifier,
|
||||
ProjectDeployBoard,
|
||||
DeployBoard,
|
||||
ProjectPublicMember,
|
||||
)
|
||||
|
||||
|
|
@ -114,7 +114,7 @@ class ProjectListSerializer(DynamicBaseSerializer):
|
|||
is_member = serializers.BooleanField(read_only=True)
|
||||
sort_order = serializers.FloatField(read_only=True)
|
||||
member_role = serializers.IntegerField(read_only=True)
|
||||
is_deployed = serializers.BooleanField(read_only=True)
|
||||
anchor = serializers.CharField(read_only=True)
|
||||
members = serializers.SerializerMethodField()
|
||||
|
||||
def get_members(self, obj):
|
||||
|
|
@ -148,7 +148,7 @@ class ProjectDetailSerializer(BaseSerializer):
|
|||
is_member = serializers.BooleanField(read_only=True)
|
||||
sort_order = serializers.FloatField(read_only=True)
|
||||
member_role = serializers.IntegerField(read_only=True)
|
||||
is_deployed = serializers.BooleanField(read_only=True)
|
||||
anchor = serializers.CharField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Project
|
||||
|
|
@ -206,14 +206,14 @@ class ProjectMemberLiteSerializer(BaseSerializer):
|
|||
read_only_fields = fields
|
||||
|
||||
|
||||
class ProjectDeployBoardSerializer(BaseSerializer):
|
||||
class DeployBoardSerializer(BaseSerializer):
|
||||
project_details = ProjectLiteSerializer(read_only=True, source="project")
|
||||
workspace_detail = WorkspaceLiteSerializer(
|
||||
read_only=True, source="workspace"
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = ProjectDeployBoard
|
||||
model = DeployBoard
|
||||
fields = "__all__"
|
||||
read_only_fields = [
|
||||
"workspace",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from django.urls import path
|
|||
from plane.app.views import (
|
||||
ProjectEstimatePointEndpoint,
|
||||
BulkEstimatePointEndpoint,
|
||||
EstimatePointEndpoint,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -34,4 +35,23 @@ urlpatterns = [
|
|||
),
|
||||
name="bulk-create-estimate-points",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/<uuid:estimate_id>/estimate-points/",
|
||||
EstimatePointEndpoint.as_view(
|
||||
{
|
||||
"post": "create",
|
||||
}
|
||||
),
|
||||
name="estimate-points",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/estimates/<uuid:estimate_id>/estimate-points/<estimate_point_id>/",
|
||||
EstimatePointEndpoint.as_view(
|
||||
{
|
||||
"patch": "partial_update",
|
||||
"delete": "destroy",
|
||||
}
|
||||
),
|
||||
name="estimate-points",
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from django.urls import path
|
|||
|
||||
from plane.app.views import (
|
||||
ProjectViewSet,
|
||||
DeployBoardViewSet,
|
||||
ProjectInvitationsViewset,
|
||||
ProjectMemberViewSet,
|
||||
ProjectMemberUserEndpoint,
|
||||
|
|
@ -12,7 +13,6 @@ from plane.app.views import (
|
|||
ProjectFavoritesViewSet,
|
||||
UserProjectInvitationsViewset,
|
||||
ProjectPublicCoverImagesEndpoint,
|
||||
ProjectDeployBoardViewSet,
|
||||
UserProjectRolesEndpoint,
|
||||
ProjectArchiveUnarchiveEndpoint,
|
||||
)
|
||||
|
|
@ -157,7 +157,7 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/project-deploy-boards/",
|
||||
ProjectDeployBoardViewSet.as_view(
|
||||
DeployBoardViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
"post": "create",
|
||||
|
|
@ -167,7 +167,7 @@ urlpatterns = [
|
|||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/project-deploy-boards/<uuid:pk>/",
|
||||
ProjectDeployBoardViewSet.as_view(
|
||||
DeployBoardViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
"patch": "partial_update",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from .project.base import (
|
|||
ProjectUserViewsEndpoint,
|
||||
ProjectFavoritesViewSet,
|
||||
ProjectPublicCoverImagesEndpoint,
|
||||
ProjectDeployBoardViewSet,
|
||||
DeployBoardViewSet,
|
||||
ProjectArchiveUnarchiveEndpoint,
|
||||
)
|
||||
|
||||
|
|
@ -190,6 +190,7 @@ from .external.base import (
|
|||
from .estimate.base import (
|
||||
ProjectEstimatePointEndpoint,
|
||||
BulkEstimatePointEndpoint,
|
||||
EstimatePointEndpoint,
|
||||
)
|
||||
|
||||
from .inbox.base import InboxViewSet, InboxIssueViewSet
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class AnalyticsEndpoint(BaseAPIView):
|
|||
"state__group",
|
||||
"labels__id",
|
||||
"assignees__id",
|
||||
"estimate_point",
|
||||
"estimate_point__value",
|
||||
"issue_cycle__cycle_id",
|
||||
"issue_module__module_id",
|
||||
"priority",
|
||||
|
|
@ -381,9 +381,9 @@ class DefaultAnalyticsEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
open_estimate_sum = open_issues_queryset.aggregate(
|
||||
sum=Sum("estimate_point")
|
||||
sum=Sum("point")
|
||||
)["sum"]
|
||||
total_estimate_sum = base_issues.aggregate(sum=Sum("estimate_point"))[
|
||||
total_estimate_sum = base_issues.aggregate(sum=Sum("point"))[
|
||||
"sum"
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -177,6 +177,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
def get(self, request, slug, project_id, pk=None):
|
||||
plot_type = request.GET.get("plot_type", "issues")
|
||||
if pk is None:
|
||||
queryset = (
|
||||
self.get_queryset()
|
||||
|
|
@ -375,6 +376,7 @@ class CycleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
queryset=queryset,
|
||||
slug=slug,
|
||||
project_id=project_id,
|
||||
plot_type=plot_type,
|
||||
cycle_id=pk,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -17,8 +17,11 @@ from django.db.models import (
|
|||
UUIDField,
|
||||
Value,
|
||||
When,
|
||||
Subquery,
|
||||
Sum,
|
||||
IntegerField,
|
||||
)
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.db.models.functions import Coalesce, Cast
|
||||
from django.utils import timezone
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
|
||||
|
|
@ -73,6 +76,89 @@ class CycleViewSet(BaseViewSet):
|
|||
project_id=self.kwargs.get("project_id"),
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
)
|
||||
backlog_estimate_point = (
|
||||
Issue.issue_objects.filter(
|
||||
estimate_point__estimate__type="points",
|
||||
state__group="backlog",
|
||||
issue_cycle__cycle_id=OuterRef("pk"),
|
||||
)
|
||||
.values("issue_cycle__cycle_id")
|
||||
.annotate(
|
||||
backlog_estimate_point=Sum(
|
||||
Cast("estimate_point__value", IntegerField())
|
||||
)
|
||||
)
|
||||
.values("backlog_estimate_point")[:1]
|
||||
)
|
||||
unstarted_estimate_point = (
|
||||
Issue.issue_objects.filter(
|
||||
estimate_point__estimate__type="points",
|
||||
state__group="unstarted",
|
||||
issue_cycle__cycle_id=OuterRef("pk"),
|
||||
)
|
||||
.values("issue_cycle__cycle_id")
|
||||
.annotate(
|
||||
unstarted_estimate_point=Sum(
|
||||
Cast("estimate_point__value", IntegerField())
|
||||
)
|
||||
)
|
||||
.values("unstarted_estimate_point")[:1]
|
||||
)
|
||||
started_estimate_point = (
|
||||
Issue.issue_objects.filter(
|
||||
estimate_point__estimate__type="points",
|
||||
state__group="started",
|
||||
issue_cycle__cycle_id=OuterRef("pk"),
|
||||
)
|
||||
.values("issue_cycle__cycle_id")
|
||||
.annotate(
|
||||
started_estimate_point=Sum(
|
||||
Cast("estimate_point__value", IntegerField())
|
||||
)
|
||||
)
|
||||
.values("started_estimate_point")[:1]
|
||||
)
|
||||
cancelled_estimate_point = (
|
||||
Issue.issue_objects.filter(
|
||||
estimate_point__estimate__type="points",
|
||||
state__group="cancelled",
|
||||
issue_cycle__cycle_id=OuterRef("pk"),
|
||||
)
|
||||
.values("issue_cycle__cycle_id")
|
||||
.annotate(
|
||||
cancelled_estimate_point=Sum(
|
||||
Cast("estimate_point__value", IntegerField())
|
||||
)
|
||||
)
|
||||
.values("cancelled_estimate_point")[:1]
|
||||
)
|
||||
completed_estimate_point = (
|
||||
Issue.issue_objects.filter(
|
||||
estimate_point__estimate__type="points",
|
||||
state__group="completed",
|
||||
issue_cycle__cycle_id=OuterRef("pk"),
|
||||
)
|
||||
.values("issue_cycle__cycle_id")
|
||||
.annotate(
|
||||
completed_estimate_points=Sum(
|
||||
Cast("estimate_point__value", IntegerField())
|
||||
)
|
||||
)
|
||||
.values("completed_estimate_points")[:1]
|
||||
)
|
||||
total_estimate_point = (
|
||||
Issue.issue_objects.filter(
|
||||
estimate_point__estimate__type="points",
|
||||
issue_cycle__cycle_id=OuterRef("pk"),
|
||||
)
|
||||
.values("issue_cycle__cycle_id")
|
||||
.annotate(
|
||||
total_estimate_points=Sum(
|
||||
Cast("estimate_point__value", IntegerField())
|
||||
)
|
||||
)
|
||||
.values("total_estimate_points")[:1]
|
||||
)
|
||||
return self.filter_queryset(
|
||||
super()
|
||||
.get_queryset()
|
||||
|
|
@ -197,12 +283,49 @@ class CycleViewSet(BaseViewSet):
|
|||
Value([], output_field=ArrayField(UUIDField())),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
backlog_estimate_points=Coalesce(
|
||||
Subquery(backlog_estimate_point),
|
||||
Value(0, output_field=IntegerField()),
|
||||
),
|
||||
)
|
||||
.annotate(
|
||||
unstarted_estimate_points=Coalesce(
|
||||
Subquery(unstarted_estimate_point),
|
||||
Value(0, output_field=IntegerField()),
|
||||
),
|
||||
)
|
||||
.annotate(
|
||||
started_estimate_points=Coalesce(
|
||||
Subquery(started_estimate_point),
|
||||
Value(0, output_field=IntegerField()),
|
||||
),
|
||||
)
|
||||
.annotate(
|
||||
cancelled_estimate_points=Coalesce(
|
||||
Subquery(cancelled_estimate_point),
|
||||
Value(0, output_field=IntegerField()),
|
||||
),
|
||||
)
|
||||
.annotate(
|
||||
completed_estimate_points=Coalesce(
|
||||
Subquery(completed_estimate_point),
|
||||
Value(0, output_field=IntegerField()),
|
||||
),
|
||||
)
|
||||
.annotate(
|
||||
total_estimate_points=Coalesce(
|
||||
Subquery(total_estimate_point),
|
||||
Value(0, output_field=IntegerField()),
|
||||
),
|
||||
)
|
||||
.order_by("-is_favorite", "name")
|
||||
.distinct()
|
||||
)
|
||||
|
||||
def list(self, request, slug, project_id):
|
||||
queryset = self.get_queryset().filter(archived_at__isnull=True)
|
||||
plot_type = request.GET.get("plot_type", "issues")
|
||||
cycle_view = request.GET.get("cycle_view", "all")
|
||||
|
||||
# Update the order by
|
||||
|
|
@ -233,6 +356,12 @@ class CycleViewSet(BaseViewSet):
|
|||
"progress_snapshot",
|
||||
"logo_props",
|
||||
# meta fields
|
||||
"backlog_estimate_points",
|
||||
"unstarted_estimate_points",
|
||||
"started_estimate_points",
|
||||
"cancelled_estimate_points",
|
||||
"completed_estimate_points",
|
||||
"total_estimate_points",
|
||||
"is_favorite",
|
||||
"total_issues",
|
||||
"cancelled_issues",
|
||||
|
|
@ -335,6 +464,7 @@ class CycleViewSet(BaseViewSet):
|
|||
queryset=queryset.first(),
|
||||
slug=slug,
|
||||
project_id=project_id,
|
||||
plot_type=plot_type,
|
||||
cycle_id=data[0]["id"],
|
||||
)
|
||||
)
|
||||
|
|
@ -359,6 +489,8 @@ class CycleViewSet(BaseViewSet):
|
|||
"progress_snapshot",
|
||||
"logo_props",
|
||||
# meta fields
|
||||
"completed_estimate_points",
|
||||
"total_estimate_points",
|
||||
"is_favorite",
|
||||
"total_issues",
|
||||
"cancelled_issues",
|
||||
|
|
@ -527,6 +659,7 @@ class CycleViewSet(BaseViewSet):
|
|||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def retrieve(self, request, slug, project_id, pk):
|
||||
plot_type = request.GET.get("plot_type", "issues")
|
||||
queryset = (
|
||||
self.get_queryset().filter(archived_at__isnull=True).filter(pk=pk)
|
||||
)
|
||||
|
|
@ -682,6 +815,7 @@ class CycleViewSet(BaseViewSet):
|
|||
queryset=queryset,
|
||||
slug=slug,
|
||||
project_id=project_id,
|
||||
plot_type=plot_type,
|
||||
cycle_id=pk,
|
||||
)
|
||||
|
||||
|
|
@ -798,6 +932,7 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
|
||||
def post(self, request, slug, project_id, cycle_id):
|
||||
new_cycle_id = request.data.get("new_cycle_id", False)
|
||||
plot_type = request.GET.get("plot_type", "issues")
|
||||
|
||||
if not new_cycle_id:
|
||||
return Response(
|
||||
|
|
@ -879,6 +1014,7 @@ class TransferCycleIssueEndpoint(BaseAPIView):
|
|||
queryset=old_cycle.first(),
|
||||
slug=slug,
|
||||
project_id=project_id,
|
||||
plot_type=plot_type,
|
||||
cycle_id=cycle_id,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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,17 @@ 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 = request.data.get('estimate')
|
||||
estimate_name = estimate.get("name", generate_random_name())
|
||||
estimate_type = estimate.get("type", 'categories')
|
||||
last_used = estimate.get("last_used", False)
|
||||
estimate = Estimate.objects.create(
|
||||
name=estimate_name, project_id=project_id, last_used=last_used, type=estimate_type
|
||||
)
|
||||
|
||||
estimate_points = request.data.get("estimate_points", [])
|
||||
|
||||
|
|
@ -67,14 +80,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(
|
||||
|
|
@ -93,17 +98,8 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||
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,
|
||||
)
|
||||
serializer = EstimateReadSerializer(estimate)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def retrieve(self, request, slug, project_id, estimate_id):
|
||||
estimate = Estimate.objects.get(
|
||||
|
|
@ -115,13 +111,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(
|
||||
|
|
@ -131,15 +124,10 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||
|
||||
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()
|
||||
if request.data.get("estimate"):
|
||||
estimate.name = request.data.get("estimate").get("name", estimate.name)
|
||||
estimate.type = request.data.get("estimate").get("type", estimate.type)
|
||||
estimate.save()
|
||||
|
||||
estimate_points_data = request.data.get("estimate_points", [])
|
||||
|
||||
|
|
@ -165,29 +153,113 @@ class BulkEstimatePointEndpoint(BaseViewSet):
|
|||
estimate_point.value = estimate_point_data[0].get(
|
||||
"value", estimate_point.value
|
||||
)
|
||||
estimate_point.key = estimate_point_data[0].get(
|
||||
"key", estimate_point.key
|
||||
)
|
||||
updated_estimate_points.append(estimate_point)
|
||||
|
||||
EstimatePoint.objects.bulk_update(
|
||||
updated_estimate_points,
|
||||
["value"],
|
||||
["key", "value"],
|
||||
batch_size=10,
|
||||
)
|
||||
|
||||
estimate_point_serializer = EstimatePointSerializer(
|
||||
estimate_points, many=True
|
||||
)
|
||||
estimate_serializer = EstimateReadSerializer(estimate)
|
||||
return Response(
|
||||
{
|
||||
"estimate": estimate_serializer.data,
|
||||
"estimate_points": estimate_point_serializer.data,
|
||||
},
|
||||
estimate_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 EstimatePointEndpoint(BaseViewSet):
|
||||
permission_classes = [
|
||||
ProjectEntityPermission,
|
||||
]
|
||||
|
||||
def create(self, request, slug, project_id, estimate_id):
|
||||
# TODO: add a key validation if the same key already exists
|
||||
if not request.data.get("key") or not request.data.get("value"):
|
||||
return Response(
|
||||
{"error": "Key and value are required"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
key = request.data.get("key", 0)
|
||||
value = request.data.get("value", "")
|
||||
estimate_point = EstimatePoint.objects.create(
|
||||
estimate_id=estimate_id,
|
||||
project_id=project_id,
|
||||
key=key,
|
||||
value=value,
|
||||
)
|
||||
serializer = EstimatePointSerializer(estimate_point).data
|
||||
return Response(serializer, status=status.HTTP_200_OK)
|
||||
|
||||
def partial_update(self, request, slug, project_id, estimate_id, estimate_point_id):
|
||||
# TODO: add a key validation if the same key already exists
|
||||
estimate_point = EstimatePoint.objects.get(
|
||||
pk=estimate_point_id,
|
||||
estimate_id=estimate_id,
|
||||
project_id=project_id,
|
||||
workspace__slug=slug,
|
||||
)
|
||||
serializer = EstimatePointSerializer(
|
||||
estimate_point, data=request.data, partial=True
|
||||
)
|
||||
if not serializer.is_valid():
|
||||
return Response(
|
||||
serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
serializer.save()
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def destroy(
|
||||
self, request, slug, project_id, estimate_id, estimate_point_id
|
||||
):
|
||||
new_estimate_id = request.GET.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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
def get(self, request, slug, project_id, pk=None):
|
||||
plot_type = request.GET.get("plot_type", "issues")
|
||||
if pk is None:
|
||||
queryset = self.get_queryset()
|
||||
modules = queryset.values( # Required fields
|
||||
|
|
@ -323,6 +324,7 @@ class ModuleArchiveUnarchiveEndpoint(BaseAPIView):
|
|||
queryset=modules,
|
||||
slug=slug,
|
||||
project_id=project_id,
|
||||
plot_type=plot_type,
|
||||
module_id=pk,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,8 +16,9 @@ from django.db.models import (
|
|||
Subquery,
|
||||
UUIDField,
|
||||
Value,
|
||||
Sum,
|
||||
)
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.db.models.functions import Coalesce, Cast
|
||||
from django.core.serializers.json import DjangoJSONEncoder
|
||||
from django.utils import timezone
|
||||
|
||||
|
|
@ -128,6 +129,34 @@ class ModuleViewSet(BaseViewSet):
|
|||
.annotate(cnt=Count("pk"))
|
||||
.values("cnt")
|
||||
)
|
||||
completed_estimate_point = (
|
||||
Issue.issue_objects.filter(
|
||||
estimate_point__estimate__type="points",
|
||||
state__group="completed",
|
||||
issue_module__module_id=OuterRef("pk"),
|
||||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
completed_estimate_points=Sum(
|
||||
Cast("estimate_point__value", IntegerField())
|
||||
)
|
||||
)
|
||||
.values("completed_estimate_points")[:1]
|
||||
)
|
||||
|
||||
total_estimate_point = (
|
||||
Issue.issue_objects.filter(
|
||||
estimate_point__estimate__type="points",
|
||||
issue_module__module_id=OuterRef("pk"),
|
||||
)
|
||||
.values("issue_module__module_id")
|
||||
.annotate(
|
||||
total_estimate_points=Sum(
|
||||
Cast("estimate_point__value", IntegerField())
|
||||
)
|
||||
)
|
||||
.values("total_estimate_points")[:1]
|
||||
)
|
||||
return (
|
||||
super()
|
||||
.get_queryset()
|
||||
|
|
@ -182,6 +211,18 @@ class ModuleViewSet(BaseViewSet):
|
|||
Value(0, output_field=IntegerField()),
|
||||
)
|
||||
)
|
||||
.annotate(
|
||||
completed_estimate_points=Coalesce(
|
||||
Subquery(completed_estimate_point),
|
||||
Value(0, output_field=IntegerField()),
|
||||
),
|
||||
)
|
||||
.annotate(
|
||||
total_estimate_points=Coalesce(
|
||||
Subquery(total_estimate_point),
|
||||
Value(0, output_field=IntegerField()),
|
||||
),
|
||||
)
|
||||
.annotate(
|
||||
member_ids=Coalesce(
|
||||
ArrayAgg(
|
||||
|
|
@ -233,6 +274,8 @@ class ModuleViewSet(BaseViewSet):
|
|||
"total_issues",
|
||||
"started_issues",
|
||||
"unstarted_issues",
|
||||
"completed_estimate_points",
|
||||
"total_estimate_points",
|
||||
"backlog_issues",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
|
|
@ -284,6 +327,8 @@ class ModuleViewSet(BaseViewSet):
|
|||
"external_id",
|
||||
"logo_props",
|
||||
# computed fields
|
||||
"completed_estimate_points",
|
||||
"total_estimate_points",
|
||||
"total_issues",
|
||||
"is_favorite",
|
||||
"cancelled_issues",
|
||||
|
|
@ -301,6 +346,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
return Response(modules, status=status.HTTP_200_OK)
|
||||
|
||||
def retrieve(self, request, slug, project_id, pk):
|
||||
plot_type = request.GET.get("plot_type", "burndown")
|
||||
queryset = (
|
||||
self.get_queryset()
|
||||
.filter(archived_at__isnull=True)
|
||||
|
|
@ -423,6 +469,7 @@ class ModuleViewSet(BaseViewSet):
|
|||
queryset=modules,
|
||||
slug=slug,
|
||||
project_id=project_id,
|
||||
plot_type=plot_type,
|
||||
module_id=pk,
|
||||
)
|
||||
|
||||
|
|
@ -469,6 +516,8 @@ class ModuleViewSet(BaseViewSet):
|
|||
"external_id",
|
||||
"logo_props",
|
||||
# computed fields
|
||||
"completed_estimate_points",
|
||||
"total_estimate_points",
|
||||
"is_favorite",
|
||||
"cancelled_issues",
|
||||
"completed_issues",
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ from plane.app.views.base import BaseViewSet, BaseAPIView
|
|||
from plane.app.serializers import (
|
||||
ProjectSerializer,
|
||||
ProjectListSerializer,
|
||||
ProjectDeployBoardSerializer,
|
||||
DeployBoardSerializer,
|
||||
)
|
||||
|
||||
from plane.app.permissions import (
|
||||
|
|
@ -46,7 +46,7 @@ from plane.db.models import (
|
|||
Module,
|
||||
Cycle,
|
||||
Inbox,
|
||||
ProjectDeployBoard,
|
||||
DeployBoard,
|
||||
IssueProperty,
|
||||
Issue,
|
||||
)
|
||||
|
|
@ -137,12 +137,11 @@ class ProjectViewSet(BaseViewSet):
|
|||
).values("role")
|
||||
)
|
||||
.annotate(
|
||||
is_deployed=Exists(
|
||||
ProjectDeployBoard.objects.filter(
|
||||
project_id=OuterRef("pk"),
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
)
|
||||
)
|
||||
anchor=DeployBoard.objects.filter(
|
||||
entity_name="project",
|
||||
entity_identifier=OuterRef("pk"),
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
).values("anchor")
|
||||
)
|
||||
.annotate(sort_order=Subquery(sort_order))
|
||||
.prefetch_related(
|
||||
|
|
@ -639,29 +638,28 @@ class ProjectPublicCoverImagesEndpoint(BaseAPIView):
|
|||
return Response(files, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ProjectDeployBoardViewSet(BaseViewSet):
|
||||
class DeployBoardViewSet(BaseViewSet):
|
||||
permission_classes = [
|
||||
ProjectMemberPermission,
|
||||
]
|
||||
serializer_class = ProjectDeployBoardSerializer
|
||||
model = ProjectDeployBoard
|
||||
serializer_class = DeployBoardSerializer
|
||||
model = DeployBoard
|
||||
|
||||
def get_queryset(self):
|
||||
return (
|
||||
super()
|
||||
.get_queryset()
|
||||
.filter(
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
)
|
||||
.select_related("project")
|
||||
)
|
||||
def list(self, request, slug, project_id):
|
||||
project_deploy_board = DeployBoard.objects.filter(
|
||||
entity_name="project",
|
||||
entity_identifier=project_id,
|
||||
workspace__slug=slug,
|
||||
).first()
|
||||
|
||||
serializer = DeployBoardSerializer(project_deploy_board)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def create(self, request, slug, project_id):
|
||||
comments = request.data.get("comments", False)
|
||||
reactions = request.data.get("reactions", False)
|
||||
comments = request.data.get("is_comments_enabled", False)
|
||||
reactions = request.data.get("is_reactions_enabled", False)
|
||||
inbox = request.data.get("inbox", None)
|
||||
votes = request.data.get("votes", False)
|
||||
votes = request.data.get("is_votes_enabled", False)
|
||||
views = request.data.get(
|
||||
"views",
|
||||
{
|
||||
|
|
@ -673,17 +671,18 @@ class ProjectDeployBoardViewSet(BaseViewSet):
|
|||
},
|
||||
)
|
||||
|
||||
project_deploy_board, _ = ProjectDeployBoard.objects.get_or_create(
|
||||
anchor=f"{slug}/{project_id}",
|
||||
project_deploy_board, _ = DeployBoard.objects.get_or_create(
|
||||
entity_name="project",
|
||||
entity_identifier=project_id,
|
||||
project_id=project_id,
|
||||
)
|
||||
project_deploy_board.comments = comments
|
||||
project_deploy_board.reactions = reactions
|
||||
project_deploy_board.inbox = inbox
|
||||
project_deploy_board.votes = votes
|
||||
project_deploy_board.views = views
|
||||
project_deploy_board.view_props = views
|
||||
project_deploy_board.is_votes_enabled = votes
|
||||
project_deploy_board.is_comments_enabled = comments
|
||||
project_deploy_board.is_reactions_enabled = reactions
|
||||
|
||||
project_deploy_board.save()
|
||||
|
||||
serializer = ProjectDeployBoardSerializer(project_deploy_board)
|
||||
serializer = DeployBoardSerializer(project_deploy_board)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ from plane.db.models import (
|
|||
Project,
|
||||
State,
|
||||
User,
|
||||
EstimatePoint,
|
||||
)
|
||||
from plane.settings.redis import redis_instance
|
||||
from plane.utils.exception_logger import log_exception
|
||||
|
|
@ -448,21 +449,37 @@ def track_estimate_points(
|
|||
if current_instance.get("estimate_point") != requested_data.get(
|
||||
"estimate_point"
|
||||
):
|
||||
old_estimate = (
|
||||
EstimatePoint.objects.filter(
|
||||
pk=current_instance.get("estimate_point")
|
||||
).first()
|
||||
if current_instance.get("estimate_point") is not None
|
||||
else None
|
||||
)
|
||||
new_estimate = (
|
||||
EstimatePoint.objects.filter(
|
||||
pk=requested_data.get("estimate_point")
|
||||
).first()
|
||||
if requested_data.get("estimate_point") is not None
|
||||
else None
|
||||
)
|
||||
issue_activities.append(
|
||||
IssueActivity(
|
||||
issue_id=issue_id,
|
||||
actor_id=actor_id,
|
||||
verb="updated",
|
||||
old_value=(
|
||||
old_identifier=(
|
||||
current_instance.get("estimate_point")
|
||||
if current_instance.get("estimate_point") is not None
|
||||
else ""
|
||||
else None
|
||||
),
|
||||
new_value=(
|
||||
new_identifier=(
|
||||
requested_data.get("estimate_point")
|
||||
if requested_data.get("estimate_point") is not None
|
||||
else ""
|
||||
else None
|
||||
),
|
||||
old_value=old_estimate.value if old_estimate else None,
|
||||
new_value=new_estimate.value if new_estimate else None,
|
||||
field="estimate_point",
|
||||
project_id=project_id,
|
||||
workspace_id=workspace_id,
|
||||
|
|
|
|||
260
apiserver/plane/db/migrations/0067_issue_estimate.py
Normal file
260
apiserver/plane/db/migrations/0067_issue_estimate.py
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
# # Generated by Django 4.2.7 on 2024-05-24 09:47
|
||||
# Python imports
|
||||
import uuid
|
||||
from uuid import uuid4
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import plane.db.models.deploy_board
|
||||
|
||||
|
||||
def issue_estimate_point(apps, schema_editor):
|
||||
Issue = apps.get_model("db", "Issue")
|
||||
Project = apps.get_model("db", "Project")
|
||||
EstimatePoint = apps.get_model("db", "EstimatePoint")
|
||||
IssueActivity = apps.get_model("db", "IssueActivity")
|
||||
updated_estimate_point = []
|
||||
updated_issue_activity = []
|
||||
|
||||
# loop through all the projects
|
||||
for project in Project.objects.filter(estimate__isnull=False):
|
||||
estimate_points = EstimatePoint.objects.filter(
|
||||
estimate=project.estimate, project=project
|
||||
)
|
||||
|
||||
for issue_activity in IssueActivity.objects.filter(
|
||||
field="estimate_point", project=project
|
||||
):
|
||||
if issue_activity.new_value:
|
||||
new_identifier = estimate_points.filter(
|
||||
key=issue_activity.new_value
|
||||
).first().id
|
||||
issue_activity.new_identifier = new_identifier
|
||||
new_value = estimate_points.filter(
|
||||
key=issue_activity.new_value
|
||||
).first().value
|
||||
issue_activity.new_value = new_value
|
||||
|
||||
if issue_activity.old_value:
|
||||
old_identifier = estimate_points.filter(
|
||||
key=issue_activity.old_value
|
||||
).first().id
|
||||
issue_activity.old_identifier = old_identifier
|
||||
old_value = estimate_points.filter(
|
||||
key=issue_activity.old_value
|
||||
).first().value
|
||||
issue_activity.old_value = old_value
|
||||
updated_issue_activity.append(issue_activity)
|
||||
|
||||
for issue in Issue.objects.filter(
|
||||
point__isnull=False, project=project
|
||||
):
|
||||
# get the estimate id for the corresponding estimate point in the issue
|
||||
estimate = estimate_points.filter(key=issue.point).first()
|
||||
issue.estimate_point = estimate
|
||||
updated_estimate_point.append(issue)
|
||||
|
||||
Issue.objects.bulk_update(
|
||||
updated_estimate_point, ["estimate_point"], batch_size=1000
|
||||
)
|
||||
IssueActivity.objects.bulk_update(
|
||||
updated_issue_activity,
|
||||
["new_value", "old_value", "new_identifier", "old_identifier"],
|
||||
batch_size=1000,
|
||||
)
|
||||
|
||||
|
||||
def last_used_estimate(apps, schema_editor):
|
||||
Project = apps.get_model("db", "Project")
|
||||
Estimate = apps.get_model("db", "Estimate")
|
||||
|
||||
# Get all estimate ids used in projects
|
||||
estimate_ids = Project.objects.filter(estimate__isnull=False).values_list(
|
||||
"estimate", flat=True
|
||||
)
|
||||
|
||||
# Update all matching estimates
|
||||
Estimate.objects.filter(id__in=estimate_ids).update(last_used=True)
|
||||
|
||||
|
||||
def populate_deploy_board(apps, schema_editor):
|
||||
DeployBoard = apps.get_model("db", "DeployBoard")
|
||||
ProjectDeployBoard = apps.get_model("db", "ProjectDeployBoard")
|
||||
|
||||
DeployBoard.objects.bulk_create(
|
||||
[
|
||||
DeployBoard(
|
||||
entity_identifier=deploy_board.project_id,
|
||||
project_id=deploy_board.project_id,
|
||||
entity_name="project",
|
||||
anchor=uuid4().hex,
|
||||
is_comments_enabled=deploy_board.comments,
|
||||
is_reactions_enabled=deploy_board.reactions,
|
||||
inbox=deploy_board.inbox,
|
||||
is_votes_enabled=deploy_board.votes,
|
||||
view_props=deploy_board.views,
|
||||
workspace_id=deploy_board.workspace_id,
|
||||
created_at=deploy_board.created_at,
|
||||
updated_at=deploy_board.updated_at,
|
||||
created_by_id=deploy_board.created_by_id,
|
||||
updated_by_id=deploy_board.updated_by_id,
|
||||
)
|
||||
for deploy_board in ProjectDeployBoard.objects.all()
|
||||
],
|
||||
batch_size=100,
|
||||
)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
("db", "0066_account_id_token_cycle_logo_props_module_logo_props"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name="DeployBoard",
|
||||
fields=[
|
||||
(
|
||||
"created_at",
|
||||
models.DateTimeField(
|
||||
auto_now_add=True, verbose_name="Created At"
|
||||
),
|
||||
),
|
||||
(
|
||||
"updated_at",
|
||||
models.DateTimeField(
|
||||
auto_now=True, verbose_name="Last Modified At"
|
||||
),
|
||||
),
|
||||
(
|
||||
"id",
|
||||
models.UUIDField(
|
||||
db_index=True,
|
||||
default=uuid.uuid4,
|
||||
editable=False,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
unique=True,
|
||||
),
|
||||
),
|
||||
("entity_identifier", models.UUIDField(null=True)),
|
||||
(
|
||||
"entity_name",
|
||||
models.CharField(
|
||||
choices=[
|
||||
("project", "Project"),
|
||||
("issue", "Issue"),
|
||||
("module", "Module"),
|
||||
("cycle", "Task"),
|
||||
("page", "Page"),
|
||||
("view", "View"),
|
||||
],
|
||||
max_length=30,
|
||||
),
|
||||
),
|
||||
(
|
||||
"anchor",
|
||||
models.CharField(
|
||||
db_index=True,
|
||||
default=plane.db.models.deploy_board.get_anchor,
|
||||
max_length=255,
|
||||
unique=True,
|
||||
),
|
||||
),
|
||||
("is_comments_enabled", models.BooleanField(default=False)),
|
||||
("is_reactions_enabled", models.BooleanField(default=False)),
|
||||
("is_votes_enabled", models.BooleanField(default=False)),
|
||||
("view_props", models.JSONField(default=dict)),
|
||||
(
|
||||
"created_by",
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="%(class)s_created_by",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="Created By",
|
||||
),
|
||||
),
|
||||
(
|
||||
"inbox",
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="board_inbox",
|
||||
to="db.inbox",
|
||||
),
|
||||
),
|
||||
(
|
||||
"project",
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="project_%(class)s",
|
||||
to="db.project",
|
||||
),
|
||||
),
|
||||
(
|
||||
"updated_by",
|
||||
models.ForeignKey(
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="%(class)s_updated_by",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="Last Modified By",
|
||||
),
|
||||
),
|
||||
(
|
||||
"workspace",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="workspace_%(class)s",
|
||||
to="db.workspace",
|
||||
),
|
||||
),
|
||||
],
|
||||
options={
|
||||
"verbose_name": "Deploy Board",
|
||||
"verbose_name_plural": "Deploy Boards",
|
||||
"db_table": "deploy_boards",
|
||||
"ordering": ("-created_at",),
|
||||
"unique_together": {("entity_name", "entity_identifier")},
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="estimate",
|
||||
name="last_used",
|
||||
field=models.BooleanField(default=False),
|
||||
),
|
||||
# Rename the existing field
|
||||
migrations.RenameField(
|
||||
model_name="issue",
|
||||
old_name="estimate_point",
|
||||
new_name="point",
|
||||
),
|
||||
# Add a new field with the original name as a foreign key
|
||||
migrations.AddField(
|
||||
model_name="issue",
|
||||
name="estimate_point",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.SET_NULL,
|
||||
related_name="issue_estimates",
|
||||
to="db.EstimatePoint",
|
||||
blank=True,
|
||||
null=True,
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="estimate",
|
||||
name="type",
|
||||
field=models.CharField(default="categories", max_length=255),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="estimatepoint",
|
||||
name="value",
|
||||
field=models.CharField(max_length=255),
|
||||
),
|
||||
migrations.RunPython(issue_estimate_point),
|
||||
migrations.RunPython(last_used_estimate),
|
||||
migrations.RunPython(populate_deploy_board),
|
||||
]
|
||||
|
|
@ -4,6 +4,7 @@ from .asset import FileAsset
|
|||
from .base import BaseModel
|
||||
from .cycle import Cycle, CycleFavorite, CycleIssue, CycleUserProperties
|
||||
from .dashboard import Dashboard, DashboardWidget, Widget
|
||||
from .deploy_board import DeployBoard
|
||||
from .estimate import Estimate, EstimatePoint
|
||||
from .exporter import ExporterHistory
|
||||
from .importer import Importer
|
||||
|
|
@ -53,13 +54,13 @@ from .page import Page, PageFavorite, PageLabel, PageLog
|
|||
from .project import (
|
||||
Project,
|
||||
ProjectBaseModel,
|
||||
ProjectDeployBoard,
|
||||
ProjectFavorite,
|
||||
ProjectIdentifier,
|
||||
ProjectMember,
|
||||
ProjectMemberInvite,
|
||||
ProjectPublicMember,
|
||||
)
|
||||
from .deploy_board import DeployBoard
|
||||
from .session import Session
|
||||
from .social_connection import SocialLoginConnection
|
||||
from .state import State
|
||||
|
|
|
|||
53
apiserver/plane/db/models/deploy_board.py
Normal file
53
apiserver/plane/db/models/deploy_board.py
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
# Python imports
|
||||
from uuid import uuid4
|
||||
|
||||
# Django imports
|
||||
from django.db import models
|
||||
|
||||
# Module imports
|
||||
from .workspace import WorkspaceBaseModel
|
||||
|
||||
|
||||
def get_anchor():
|
||||
return uuid4().hex
|
||||
|
||||
|
||||
class DeployBoard(WorkspaceBaseModel):
|
||||
TYPE_CHOICES = (
|
||||
("project", "Project"),
|
||||
("issue", "Issue"),
|
||||
("module", "Module"),
|
||||
("cycle", "Task"),
|
||||
("page", "Page"),
|
||||
("view", "View"),
|
||||
)
|
||||
|
||||
entity_identifier = models.UUIDField(null=True)
|
||||
entity_name = models.CharField(
|
||||
max_length=30,
|
||||
choices=TYPE_CHOICES,
|
||||
)
|
||||
anchor = models.CharField(
|
||||
max_length=255, default=get_anchor, unique=True, db_index=True
|
||||
)
|
||||
is_comments_enabled = models.BooleanField(default=False)
|
||||
is_reactions_enabled = models.BooleanField(default=False)
|
||||
inbox = models.ForeignKey(
|
||||
"db.Inbox",
|
||||
related_name="board_inbox",
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
)
|
||||
is_votes_enabled = models.BooleanField(default=False)
|
||||
view_props = models.JSONField(default=dict)
|
||||
|
||||
def __str__(self):
|
||||
"""Return name of the deploy board"""
|
||||
return f"{self.entity_identifier} <{self.entity_name}>"
|
||||
|
||||
class Meta:
|
||||
unique_together = ["entity_name", "entity_identifier"]
|
||||
verbose_name = "Deploy Board"
|
||||
verbose_name_plural = "Deploy Boards"
|
||||
db_table = "deploy_boards"
|
||||
ordering = ("-created_at",)
|
||||
|
|
@ -11,7 +11,8 @@ class Estimate(ProjectBaseModel):
|
|||
description = models.TextField(
|
||||
verbose_name="Estimate Description", blank=True
|
||||
)
|
||||
type = models.CharField(max_length=255, default="Categories")
|
||||
type = models.CharField(max_length=255, default="categories")
|
||||
last_used = models.BooleanField(default=False)
|
||||
|
||||
def __str__(self):
|
||||
"""Return name of the estimate"""
|
||||
|
|
@ -35,7 +36,7 @@ class EstimatePoint(ProjectBaseModel):
|
|||
default=0, validators=[MinValueValidator(0), MaxValueValidator(12)]
|
||||
)
|
||||
description = models.TextField(blank=True)
|
||||
value = models.CharField(max_length=20)
|
||||
value = models.CharField(max_length=255)
|
||||
|
||||
def __str__(self):
|
||||
"""Return name of the estimate"""
|
||||
|
|
|
|||
|
|
@ -119,11 +119,18 @@ class Issue(ProjectBaseModel):
|
|||
blank=True,
|
||||
related_name="state_issue",
|
||||
)
|
||||
estimate_point = models.IntegerField(
|
||||
point = models.IntegerField(
|
||||
validators=[MinValueValidator(0), MaxValueValidator(12)],
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
estimate_point = models.ForeignKey(
|
||||
"db.EstimatePoint",
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="issue_estimates",
|
||||
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="<p></p>")
|
||||
|
|
|
|||
|
|
@ -260,6 +260,8 @@ def get_default_views():
|
|||
}
|
||||
|
||||
|
||||
# DEPRECATED TODO:
|
||||
# used to get the old anchors for the project deploy boards
|
||||
class ProjectDeployBoard(ProjectBaseModel):
|
||||
anchor = models.CharField(
|
||||
max_length=255, default=get_anchor, unique=True, db_index=True
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from plane.space.views import (
|
|||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/project-boards/<uuid:project_id>/inboxes/<uuid:inbox_id>/inbox-issues/",
|
||||
"anchor/<str:anchor>/inboxes/<uuid:inbox_id>/inbox-issues/",
|
||||
InboxIssuePublicViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
|
|
@ -20,7 +20,7 @@ urlpatterns = [
|
|||
name="inbox-issue",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/project-boards/<uuid:project_id>/inboxes/<uuid:inbox_id>/inbox-issues/<uuid:pk>/",
|
||||
"anchor/<str:anchor>/inboxes/<uuid:inbox_id>/inbox-issues/<uuid:pk>/",
|
||||
InboxIssuePublicViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
|
|
@ -31,7 +31,7 @@ urlpatterns = [
|
|||
name="inbox-issue",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/votes/",
|
||||
"anchor/<str:anchor>/issues/<uuid:issue_id>/votes/",
|
||||
IssueVotePublicViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ from plane.space.views import (
|
|||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/",
|
||||
"anchor/<str:anchor>/issues/<uuid:issue_id>/",
|
||||
IssueRetrievePublicEndpoint.as_view(),
|
||||
name="workspace-project-boards",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/comments/",
|
||||
"anchor/<str:anchor>/issues/<uuid:issue_id>/comments/",
|
||||
IssueCommentPublicViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
|
|
@ -25,7 +25,7 @@ urlpatterns = [
|
|||
name="issue-comments-project-board",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/comments/<uuid:pk>/",
|
||||
"anchor/<str:anchor>/issues/<uuid:issue_id>/comments/<uuid:pk>/",
|
||||
IssueCommentPublicViewSet.as_view(
|
||||
{
|
||||
"get": "retrieve",
|
||||
|
|
@ -36,7 +36,7 @@ urlpatterns = [
|
|||
name="issue-comments-project-board",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/reactions/",
|
||||
"anchor/<str:anchor>/issues/<uuid:issue_id>/reactions/",
|
||||
IssueReactionPublicViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
|
|
@ -46,7 +46,7 @@ urlpatterns = [
|
|||
name="issue-reactions-project-board",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/<uuid:issue_id>/reactions/<str:reaction_code>/",
|
||||
"anchor/<str:anchor>/issues/<uuid:issue_id>/reactions/<str:reaction_code>/",
|
||||
IssueReactionPublicViewSet.as_view(
|
||||
{
|
||||
"delete": "destroy",
|
||||
|
|
@ -55,7 +55,7 @@ urlpatterns = [
|
|||
name="issue-reactions-project-board",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/project-boards/<uuid:project_id>/comments/<uuid:comment_id>/reactions/",
|
||||
"anchor/<str:anchor>/comments/<uuid:comment_id>/reactions/",
|
||||
CommentReactionPublicViewSet.as_view(
|
||||
{
|
||||
"get": "list",
|
||||
|
|
@ -65,7 +65,7 @@ urlpatterns = [
|
|||
name="comment-reactions-project-board",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/project-boards/<uuid:project_id>/comments/<uuid:comment_id>/reactions/<str:reaction_code>/",
|
||||
"anchor/<str:anchor>/comments/<uuid:comment_id>/reactions/<str:reaction_code>/",
|
||||
CommentReactionPublicViewSet.as_view(
|
||||
{
|
||||
"delete": "destroy",
|
||||
|
|
|
|||
|
|
@ -4,17 +4,23 @@ from django.urls import path
|
|||
from plane.space.views import (
|
||||
ProjectDeployBoardPublicSettingsEndpoint,
|
||||
ProjectIssuesPublicEndpoint,
|
||||
WorkspaceProjectAnchorEndpoint,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path(
|
||||
"workspaces/<str:slug>/project-boards/<uuid:project_id>/settings/",
|
||||
"anchor/<str:anchor>/settings/",
|
||||
ProjectDeployBoardPublicSettingsEndpoint.as_view(),
|
||||
name="project-deploy-board-settings",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/project-boards/<uuid:project_id>/issues/",
|
||||
"anchor/<str:anchor>/issues/",
|
||||
ProjectIssuesPublicEndpoint.as_view(),
|
||||
name="project-deploy-board",
|
||||
),
|
||||
path(
|
||||
"workspaces/<str:slug>/projects/<uuid:project_id>/anchor/",
|
||||
WorkspaceProjectAnchorEndpoint.as_view(),
|
||||
name="project-deploy-board",
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from .project import (
|
||||
ProjectDeployBoardPublicSettingsEndpoint,
|
||||
WorkspaceProjectDeployBoardEndpoint,
|
||||
WorkspaceProjectAnchorEndpoint,
|
||||
)
|
||||
|
||||
from .issue import (
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from plane.db.models import (
|
|||
State,
|
||||
IssueLink,
|
||||
IssueAttachment,
|
||||
ProjectDeployBoard,
|
||||
DeployBoard,
|
||||
)
|
||||
from plane.app.serializers import (
|
||||
IssueSerializer,
|
||||
|
|
@ -39,7 +39,7 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
|||
]
|
||||
|
||||
def get_queryset(self):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
)
|
||||
|
|
@ -58,9 +58,9 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
|||
)
|
||||
return InboxIssue.objects.none()
|
||||
|
||||
def list(self, request, slug, project_id, inbox_id):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
def list(self, request, anchor, inbox_id):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
if project_deploy_board.inbox is None:
|
||||
return Response(
|
||||
|
|
@ -72,8 +72,8 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
|||
issues = (
|
||||
Issue.objects.filter(
|
||||
issue_inbox__inbox_id=inbox_id,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
)
|
||||
.filter(**filters)
|
||||
.annotate(bridge_id=F("issue_inbox__id"))
|
||||
|
|
@ -117,9 +117,9 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
|||
status=status.HTTP_200_OK,
|
||||
)
|
||||
|
||||
def create(self, request, slug, project_id, inbox_id):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
def create(self, request, anchor, inbox_id):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
if project_deploy_board.inbox is None:
|
||||
return Response(
|
||||
|
|
@ -151,7 +151,7 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
|||
name="Triage",
|
||||
group="backlog",
|
||||
description="Default state for managing all Inbox Issues",
|
||||
project_id=project_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
color="#ff7700",
|
||||
)
|
||||
|
||||
|
|
@ -163,7 +163,7 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
|||
"description_html", "<p></p>"
|
||||
),
|
||||
priority=request.data.get("issue", {}).get("priority", "low"),
|
||||
project_id=project_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
state=state,
|
||||
)
|
||||
|
||||
|
|
@ -173,14 +173,14 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
|||
requested_data=json.dumps(request.data, cls=DjangoJSONEncoder),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue.id),
|
||||
project_id=str(project_id),
|
||||
project_id=str(project_deploy_board.project_id),
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
# create an inbox issue
|
||||
InboxIssue.objects.create(
|
||||
inbox_id=inbox_id,
|
||||
project_id=project_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
issue=issue,
|
||||
source=request.data.get("source", "in-app"),
|
||||
)
|
||||
|
|
@ -188,9 +188,9 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
|||
serializer = IssueStateInboxSerializer(issue)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def partial_update(self, request, slug, project_id, inbox_id, pk):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
def partial_update(self, request, anchor, inbox_id, pk):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
if project_deploy_board.inbox is None:
|
||||
return Response(
|
||||
|
|
@ -200,8 +200,8 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
|||
|
||||
inbox_issue = InboxIssue.objects.get(
|
||||
pk=pk,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
inbox_id=inbox_id,
|
||||
)
|
||||
# Get the project member
|
||||
|
|
@ -216,8 +216,8 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
|||
|
||||
issue = Issue.objects.get(
|
||||
pk=inbox_issue.issue_id,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
)
|
||||
# viewers and guests since only viewers and guests
|
||||
issue_data = {
|
||||
|
|
@ -242,7 +242,7 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
|||
requested_data=requested_data,
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue.id),
|
||||
project_id=str(project_id),
|
||||
project_id=str(project_deploy_board.project_id),
|
||||
current_instance=json.dumps(
|
||||
IssueSerializer(current_instance).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
|
|
@ -255,9 +255,9 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
|||
issue_serializer.errors, status=status.HTTP_400_BAD_REQUEST
|
||||
)
|
||||
|
||||
def retrieve(self, request, slug, project_id, inbox_id, pk):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
def retrieve(self, request, anchor, inbox_id, pk):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
if project_deploy_board.inbox is None:
|
||||
return Response(
|
||||
|
|
@ -267,21 +267,21 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
|||
|
||||
inbox_issue = InboxIssue.objects.get(
|
||||
pk=pk,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
inbox_id=inbox_id,
|
||||
)
|
||||
issue = Issue.objects.get(
|
||||
pk=inbox_issue.issue_id,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
)
|
||||
serializer = IssueStateInboxSerializer(issue)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
def destroy(self, request, slug, project_id, inbox_id, pk):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
def destroy(self, request, anchor, inbox_id, pk):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
if project_deploy_board.inbox is None:
|
||||
return Response(
|
||||
|
|
@ -291,8 +291,8 @@ class InboxIssuePublicViewSet(BaseViewSet):
|
|||
|
||||
inbox_issue = InboxIssue.objects.get(
|
||||
pk=pk,
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
inbox_id=inbox_id,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ from plane.db.models import (
|
|||
ProjectMember,
|
||||
IssueReaction,
|
||||
CommentReaction,
|
||||
ProjectDeployBoard,
|
||||
DeployBoard,
|
||||
IssueVote,
|
||||
ProjectPublicMember,
|
||||
)
|
||||
|
|
@ -76,15 +76,15 @@ class IssueCommentPublicViewSet(BaseViewSet):
|
|||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=self.kwargs.get("anchor"),
|
||||
entity_name="project",
|
||||
)
|
||||
if project_deploy_board.comments:
|
||||
if project_deploy_board.is_comments_enabled:
|
||||
return self.filter_queryset(
|
||||
super()
|
||||
.get_queryset()
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.filter(workspace_id=project_deploy_board.workspace_id)
|
||||
.filter(issue_id=self.kwargs.get("issue_id"))
|
||||
.filter(access="EXTERNAL")
|
||||
.select_related("project")
|
||||
|
|
@ -93,8 +93,8 @@ class IssueCommentPublicViewSet(BaseViewSet):
|
|||
.annotate(
|
||||
is_member=Exists(
|
||||
ProjectMember.objects.filter(
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
member_id=self.request.user.id,
|
||||
is_active=True,
|
||||
)
|
||||
|
|
@ -103,15 +103,15 @@ class IssueCommentPublicViewSet(BaseViewSet):
|
|||
.distinct()
|
||||
).order_by("created_at")
|
||||
return IssueComment.objects.none()
|
||||
except ProjectDeployBoard.DoesNotExist:
|
||||
except DeployBoard.DoesNotExist:
|
||||
return IssueComment.objects.none()
|
||||
|
||||
def create(self, request, slug, project_id, issue_id):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
def create(self, request, anchor, issue_id):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
|
||||
if not project_deploy_board.comments:
|
||||
if not project_deploy_board.is_comments_enabled:
|
||||
return Response(
|
||||
{"error": "Comments are not enabled for this project"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
|
|
@ -120,7 +120,7 @@ class IssueCommentPublicViewSet(BaseViewSet):
|
|||
serializer = IssueCommentSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(
|
||||
project_id=project_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
issue_id=issue_id,
|
||||
actor=request.user,
|
||||
access="EXTERNAL",
|
||||
|
|
@ -132,37 +132,35 @@ class IssueCommentPublicViewSet(BaseViewSet):
|
|||
),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue_id),
|
||||
project_id=str(project_id),
|
||||
project_id=str(project_deploy_board.project_id),
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
if not ProjectMember.objects.filter(
|
||||
project_id=project_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
).exists():
|
||||
# Add the user for workspace tracking
|
||||
_ = ProjectPublicMember.objects.get_or_create(
|
||||
project_id=project_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
member=request.user,
|
||||
)
|
||||
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def partial_update(self, request, slug, project_id, issue_id, pk):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
def partial_update(self, request, anchor, issue_id, pk):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
|
||||
if not project_deploy_board.comments:
|
||||
if not project_deploy_board.is_comments_enabled:
|
||||
return Response(
|
||||
{"error": "Comments are not enabled for this project"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
comment = IssueComment.objects.get(
|
||||
workspace__slug=slug, pk=pk, actor=request.user
|
||||
)
|
||||
comment = IssueComment.objects.get(pk=pk, actor=request.user)
|
||||
serializer = IssueCommentSerializer(
|
||||
comment, data=request.data, partial=True
|
||||
)
|
||||
|
|
@ -173,7 +171,7 @@ class IssueCommentPublicViewSet(BaseViewSet):
|
|||
requested_data=json.dumps(request.data, cls=DjangoJSONEncoder),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue_id),
|
||||
project_id=str(project_id),
|
||||
project_id=str(project_deploy_board.project_id),
|
||||
current_instance=json.dumps(
|
||||
IssueCommentSerializer(comment).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
|
|
@ -183,20 +181,18 @@ class IssueCommentPublicViewSet(BaseViewSet):
|
|||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def destroy(self, request, slug, project_id, issue_id, pk):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
def destroy(self, request, anchor, issue_id, pk):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
|
||||
if not project_deploy_board.comments:
|
||||
if not project_deploy_board.is_comments_enabled:
|
||||
return Response(
|
||||
{"error": "Comments are not enabled for this project"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
comment = IssueComment.objects.get(
|
||||
workspace__slug=slug,
|
||||
pk=pk,
|
||||
project_id=project_id,
|
||||
actor=request.user,
|
||||
)
|
||||
issue_activity.delay(
|
||||
|
|
@ -204,7 +200,7 @@ class IssueCommentPublicViewSet(BaseViewSet):
|
|||
requested_data=json.dumps({"comment_id": str(pk)}),
|
||||
actor_id=str(request.user.id),
|
||||
issue_id=str(issue_id),
|
||||
project_id=str(project_id),
|
||||
project_id=str(project_deploy_board.project_id),
|
||||
current_instance=json.dumps(
|
||||
IssueCommentSerializer(comment).data,
|
||||
cls=DjangoJSONEncoder,
|
||||
|
|
@ -221,11 +217,11 @@ class IssueReactionPublicViewSet(BaseViewSet):
|
|||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
)
|
||||
if project_deploy_board.reactions:
|
||||
if project_deploy_board.is_reactions_enabled:
|
||||
return (
|
||||
super()
|
||||
.get_queryset()
|
||||
|
|
@ -236,15 +232,15 @@ class IssueReactionPublicViewSet(BaseViewSet):
|
|||
.distinct()
|
||||
)
|
||||
return IssueReaction.objects.none()
|
||||
except ProjectDeployBoard.DoesNotExist:
|
||||
except DeployBoard.DoesNotExist:
|
||||
return IssueReaction.objects.none()
|
||||
|
||||
def create(self, request, slug, project_id, issue_id):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
def create(self, request, anchor, issue_id):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
|
||||
if not project_deploy_board.reactions:
|
||||
if not project_deploy_board.is_reactions_enabled:
|
||||
return Response(
|
||||
{"error": "Reactions are not enabled for this project board"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
|
|
@ -253,16 +249,18 @@ class IssueReactionPublicViewSet(BaseViewSet):
|
|||
serializer = IssueReactionSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(
|
||||
project_id=project_id, issue_id=issue_id, actor=request.user
|
||||
project_id=project_deploy_board.project_id,
|
||||
issue_id=issue_id,
|
||||
actor=request.user,
|
||||
)
|
||||
if not ProjectMember.objects.filter(
|
||||
project_id=project_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
).exists():
|
||||
# Add the user for workspace tracking
|
||||
_ = ProjectPublicMember.objects.get_or_create(
|
||||
project_id=project_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
member=request.user,
|
||||
)
|
||||
issue_activity.delay(
|
||||
|
|
@ -272,25 +270,25 @@ class IssueReactionPublicViewSet(BaseViewSet):
|
|||
),
|
||||
actor_id=str(self.request.user.id),
|
||||
issue_id=str(self.kwargs.get("issue_id", None)),
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
project_id=str(project_deploy_board.project_id),
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def destroy(self, request, slug, project_id, issue_id, reaction_code):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
def destroy(self, request, anchor, issue_id, reaction_code):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
|
||||
if not project_deploy_board.reactions:
|
||||
if not project_deploy_board.is_reactions_enabled:
|
||||
return Response(
|
||||
{"error": "Reactions are not enabled for this project board"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
issue_reaction = IssueReaction.objects.get(
|
||||
workspace__slug=slug,
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
issue_id=issue_id,
|
||||
reaction=reaction_code,
|
||||
actor=request.user,
|
||||
|
|
@ -300,7 +298,7 @@ class IssueReactionPublicViewSet(BaseViewSet):
|
|||
requested_data=None,
|
||||
actor_id=str(self.request.user.id),
|
||||
issue_id=str(self.kwargs.get("issue_id", None)),
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
project_id=str(project_deploy_board.project_id),
|
||||
current_instance=json.dumps(
|
||||
{
|
||||
"reaction": str(reaction_code),
|
||||
|
|
@ -319,30 +317,29 @@ class CommentReactionPublicViewSet(BaseViewSet):
|
|||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=self.kwargs.get("anchor"), entity_name="project"
|
||||
)
|
||||
if project_deploy_board.reactions:
|
||||
if project_deploy_board.is_reactions_enabled:
|
||||
return (
|
||||
super()
|
||||
.get_queryset()
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.filter(project_id=self.kwargs.get("project_id"))
|
||||
.filter(workspace_id=project_deploy_board.workspace_id)
|
||||
.filter(project_id=project_deploy_board.project_id)
|
||||
.filter(comment_id=self.kwargs.get("comment_id"))
|
||||
.order_by("-created_at")
|
||||
.distinct()
|
||||
)
|
||||
return CommentReaction.objects.none()
|
||||
except ProjectDeployBoard.DoesNotExist:
|
||||
except DeployBoard.DoesNotExist:
|
||||
return CommentReaction.objects.none()
|
||||
|
||||
def create(self, request, slug, project_id, comment_id):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
def create(self, request, anchor, comment_id):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
|
||||
if not project_deploy_board.reactions:
|
||||
if not project_deploy_board.is_reactions_enabled:
|
||||
return Response(
|
||||
{"error": "Reactions are not enabled for this board"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
|
|
@ -351,18 +348,18 @@ class CommentReactionPublicViewSet(BaseViewSet):
|
|||
serializer = CommentReactionSerializer(data=request.data)
|
||||
if serializer.is_valid():
|
||||
serializer.save(
|
||||
project_id=project_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
comment_id=comment_id,
|
||||
actor=request.user,
|
||||
)
|
||||
if not ProjectMember.objects.filter(
|
||||
project_id=project_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
).exists():
|
||||
# Add the user for workspace tracking
|
||||
_ = ProjectPublicMember.objects.get_or_create(
|
||||
project_id=project_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
member=request.user,
|
||||
)
|
||||
issue_activity.delay(
|
||||
|
|
@ -379,19 +376,19 @@ class CommentReactionPublicViewSet(BaseViewSet):
|
|||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def destroy(self, request, slug, project_id, comment_id, reaction_code):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
def destroy(self, request, anchor, comment_id, reaction_code):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
if not project_deploy_board.reactions:
|
||||
if not project_deploy_board.is_reactions_enabled:
|
||||
return Response(
|
||||
{"error": "Reactions are not enabled for this board"},
|
||||
status=status.HTTP_400_BAD_REQUEST,
|
||||
)
|
||||
|
||||
comment_reaction = CommentReaction.objects.get(
|
||||
project_id=project_id,
|
||||
workspace__slug=slug,
|
||||
project_id=project_deploy_board.project_id,
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
comment_id=comment_id,
|
||||
reaction=reaction_code,
|
||||
actor=request.user,
|
||||
|
|
@ -401,7 +398,7 @@ class CommentReactionPublicViewSet(BaseViewSet):
|
|||
requested_data=None,
|
||||
actor_id=str(self.request.user.id),
|
||||
issue_id=None,
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
project_id=str(project_deploy_board.project_id),
|
||||
current_instance=json.dumps(
|
||||
{
|
||||
"reaction": str(reaction_code),
|
||||
|
|
@ -421,36 +418,42 @@ class IssueVotePublicViewSet(BaseViewSet):
|
|||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=self.kwargs.get("slug"),
|
||||
project_id=self.kwargs.get("project_id"),
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
workspace__slug=self.kwargs.get("anchor"),
|
||||
entity_name="project",
|
||||
)
|
||||
if project_deploy_board.votes:
|
||||
if project_deploy_board.is_votes_enabled:
|
||||
return (
|
||||
super()
|
||||
.get_queryset()
|
||||
.filter(issue_id=self.kwargs.get("issue_id"))
|
||||
.filter(workspace__slug=self.kwargs.get("slug"))
|
||||
.filter(project_id=self.kwargs.get("project_id"))
|
||||
.filter(workspace_id=project_deploy_board.workspace_id)
|
||||
.filter(project_id=project_deploy_board.project_id)
|
||||
)
|
||||
return IssueVote.objects.none()
|
||||
except ProjectDeployBoard.DoesNotExist:
|
||||
except DeployBoard.DoesNotExist:
|
||||
return IssueVote.objects.none()
|
||||
|
||||
def create(self, request, slug, project_id, issue_id):
|
||||
def create(self, request, anchor, issue_id):
|
||||
print("hite")
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
print("awer")
|
||||
issue_vote, _ = IssueVote.objects.get_or_create(
|
||||
actor_id=request.user.id,
|
||||
project_id=project_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
issue_id=issue_id,
|
||||
)
|
||||
print("AWer")
|
||||
# Add the user for workspace tracking
|
||||
if not ProjectMember.objects.filter(
|
||||
project_id=project_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
member=request.user,
|
||||
is_active=True,
|
||||
).exists():
|
||||
_ = ProjectPublicMember.objects.get_or_create(
|
||||
project_id=project_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
member=request.user,
|
||||
)
|
||||
issue_vote.vote = request.data.get("vote", 1)
|
||||
|
|
@ -462,26 +465,29 @@ class IssueVotePublicViewSet(BaseViewSet):
|
|||
),
|
||||
actor_id=str(self.request.user.id),
|
||||
issue_id=str(self.kwargs.get("issue_id", None)),
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
project_id=str(project_deploy_board.project_id),
|
||||
current_instance=None,
|
||||
epoch=int(timezone.now().timestamp()),
|
||||
)
|
||||
serializer = IssueVoteSerializer(issue_vote)
|
||||
return Response(serializer.data, status=status.HTTP_201_CREATED)
|
||||
|
||||
def destroy(self, request, slug, project_id, issue_id):
|
||||
def destroy(self, request, anchor, issue_id):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
issue_vote = IssueVote.objects.get(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_id=issue_id,
|
||||
actor_id=request.user.id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
)
|
||||
issue_activity.delay(
|
||||
type="issue_vote.activity.deleted",
|
||||
requested_data=None,
|
||||
actor_id=str(self.request.user.id),
|
||||
issue_id=str(self.kwargs.get("issue_id", None)),
|
||||
project_id=str(self.kwargs.get("project_id", None)),
|
||||
project_id=str(project_deploy_board.project_id),
|
||||
current_instance=json.dumps(
|
||||
{
|
||||
"vote": str(issue_vote.vote),
|
||||
|
|
@ -499,9 +505,14 @@ class IssueRetrievePublicEndpoint(BaseAPIView):
|
|||
AllowAny,
|
||||
]
|
||||
|
||||
def get(self, request, slug, project_id, issue_id):
|
||||
def get(self, request, anchor, issue_id):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
issue = Issue.objects.get(
|
||||
workspace__slug=slug, project_id=project_id, pk=issue_id
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
pk=issue_id,
|
||||
)
|
||||
serializer = IssuePublicSerializer(issue)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
|
@ -512,14 +523,17 @@ class ProjectIssuesPublicEndpoint(BaseAPIView):
|
|||
AllowAny,
|
||||
]
|
||||
|
||||
def get(self, request, slug, project_id):
|
||||
if not ProjectDeployBoard.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
def get(self, request, anchor):
|
||||
if not DeployBoard.objects.filter(
|
||||
anchor=anchor, entity_name="project"
|
||||
).exists():
|
||||
return Response(
|
||||
{"error": "Project is not published"},
|
||||
status=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
|
||||
filters = issue_filters(request.query_params, "GET")
|
||||
|
||||
|
|
@ -544,8 +558,8 @@ class ProjectIssuesPublicEndpoint(BaseAPIView):
|
|||
.annotate(count=Func(F("id"), function="Count"))
|
||||
.values("count")
|
||||
)
|
||||
.filter(project_id=project_id)
|
||||
.filter(workspace__slug=slug)
|
||||
.filter(project_id=project_deploy_board.project_id)
|
||||
.filter(workspace_id=project_deploy_board.workspace_id)
|
||||
.select_related("project", "workspace", "state", "parent")
|
||||
.prefetch_related("assignees", "labels")
|
||||
.prefetch_related(
|
||||
|
|
@ -652,8 +666,8 @@ class ProjectIssuesPublicEndpoint(BaseAPIView):
|
|||
states = (
|
||||
State.objects.filter(
|
||||
~Q(name="Triage"),
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
)
|
||||
.annotate(
|
||||
custom_order=Case(
|
||||
|
|
@ -670,7 +684,8 @@ class ProjectIssuesPublicEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
labels = Label.objects.filter(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
workspace_id=project_deploy_board.workspace_id,
|
||||
project_id=project_deploy_board.project_id,
|
||||
).values("id", "name", "color", "parent")
|
||||
|
||||
## Grouping the results
|
||||
|
|
|
|||
|
|
@ -11,10 +11,10 @@ from rest_framework.permissions import AllowAny
|
|||
|
||||
# Module imports
|
||||
from .base import BaseAPIView
|
||||
from plane.app.serializers import ProjectDeployBoardSerializer
|
||||
from plane.app.serializers import DeployBoardSerializer
|
||||
from plane.db.models import (
|
||||
Project,
|
||||
ProjectDeployBoard,
|
||||
DeployBoard,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -23,11 +23,11 @@ class ProjectDeployBoardPublicSettingsEndpoint(BaseAPIView):
|
|||
AllowAny,
|
||||
]
|
||||
|
||||
def get(self, request, slug, project_id):
|
||||
project_deploy_board = ProjectDeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
def get(self, request, anchor):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
anchor=anchor, entity_name="project"
|
||||
)
|
||||
serializer = ProjectDeployBoardSerializer(project_deploy_board)
|
||||
serializer = DeployBoardSerializer(project_deploy_board)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
|
|
@ -36,13 +36,16 @@ class WorkspaceProjectDeployBoardEndpoint(BaseAPIView):
|
|||
AllowAny,
|
||||
]
|
||||
|
||||
def get(self, request, slug):
|
||||
def get(self, request, anchor):
|
||||
deploy_board = DeployBoard.objects.filter(anchor=anchor, entity_name="project").values_list
|
||||
projects = (
|
||||
Project.objects.filter(workspace__slug=slug)
|
||||
Project.objects.filter(workspace=deploy_board.workspace)
|
||||
.annotate(
|
||||
is_public=Exists(
|
||||
ProjectDeployBoard.objects.filter(
|
||||
workspace__slug=slug, project_id=OuterRef("pk")
|
||||
DeployBoard.objects.filter(
|
||||
anchor=anchor,
|
||||
project_id=OuterRef("pk"),
|
||||
entity_name="project",
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
@ -58,3 +61,16 @@ class WorkspaceProjectDeployBoardEndpoint(BaseAPIView):
|
|||
)
|
||||
|
||||
return Response(projects, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class WorkspaceProjectAnchorEndpoint(BaseAPIView):
|
||||
permission_classes = [
|
||||
AllowAny,
|
||||
]
|
||||
|
||||
def get(self, request, slug, project_id):
|
||||
project_deploy_board = DeployBoard.objects.get(
|
||||
workspace__slug=slug, project_id=project_id
|
||||
)
|
||||
serializer = DeployBoardSerializer(project_deploy_board)
|
||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||
|
|
|
|||
|
|
@ -4,18 +4,28 @@ from itertools import groupby
|
|||
|
||||
# Django import
|
||||
from django.db import models
|
||||
from django.db.models import Case, CharField, Count, F, Sum, Value, When
|
||||
from django.db.models import (
|
||||
Case,
|
||||
CharField,
|
||||
Count,
|
||||
F,
|
||||
Sum,
|
||||
Value,
|
||||
When,
|
||||
IntegerField,
|
||||
)
|
||||
from django.db.models.functions import (
|
||||
Coalesce,
|
||||
Concat,
|
||||
ExtractMonth,
|
||||
ExtractYear,
|
||||
TruncDate,
|
||||
Cast,
|
||||
)
|
||||
from django.utils import timezone
|
||||
|
||||
# Module imports
|
||||
from plane.db.models import Issue
|
||||
from plane.db.models import Issue, Project
|
||||
|
||||
|
||||
def annotate_with_monthly_dimension(queryset, field_name, attribute):
|
||||
|
|
@ -87,9 +97,9 @@ def build_graph_plot(queryset, x_axis, y_axis, segment=None):
|
|||
|
||||
# Estimate
|
||||
else:
|
||||
queryset = queryset.annotate(estimate=Sum("estimate_point")).order_by(
|
||||
x_axis
|
||||
)
|
||||
queryset = queryset.annotate(
|
||||
estimate=Sum(Cast("estimate_point__value", IntegerField()))
|
||||
).order_by(x_axis)
|
||||
queryset = (
|
||||
queryset.annotate(segment=F(segment)) if segment else queryset
|
||||
)
|
||||
|
|
@ -110,9 +120,33 @@ def build_graph_plot(queryset, x_axis, y_axis, segment=None):
|
|||
return sort_data(grouped_data, temp_axis)
|
||||
|
||||
|
||||
def burndown_plot(queryset, slug, project_id, cycle_id=None, module_id=None):
|
||||
def burndown_plot(
|
||||
queryset,
|
||||
slug,
|
||||
project_id,
|
||||
plot_type,
|
||||
cycle_id=None,
|
||||
module_id=None,
|
||||
):
|
||||
# Total Issues in Cycle or Module
|
||||
total_issues = queryset.total_issues
|
||||
# check whether the estimate is a point or not
|
||||
estimate_type = Project.objects.filter(
|
||||
workspace__slug=slug,
|
||||
pk=project_id,
|
||||
estimate__isnull=False,
|
||||
estimate__type="points",
|
||||
).exists()
|
||||
if estimate_type and plot_type == "points":
|
||||
issue_estimates = Issue.objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_cycle__cycle_id=cycle_id,
|
||||
estimate_point__isnull=False,
|
||||
).values_list("estimate_point__value", flat=True)
|
||||
|
||||
issue_estimates = [int(value) for value in issue_estimates]
|
||||
total_estimate_points = sum(issue_estimates)
|
||||
|
||||
if cycle_id:
|
||||
if queryset.end_date and queryset.start_date:
|
||||
|
|
@ -128,18 +162,32 @@ def burndown_plot(queryset, slug, project_id, cycle_id=None, module_id=None):
|
|||
|
||||
chart_data = {str(date): 0 for date in date_range}
|
||||
|
||||
completed_issues_distribution = (
|
||||
Issue.issue_objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_cycle__cycle_id=cycle_id,
|
||||
if plot_type == "points":
|
||||
completed_issues_estimate_point_distribution = (
|
||||
Issue.issue_objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_cycle__cycle_id=cycle_id,
|
||||
estimate_point__isnull=False,
|
||||
)
|
||||
.annotate(date=TruncDate("completed_at"))
|
||||
.values("date")
|
||||
.values("date", "estimate_point__value")
|
||||
.order_by("date")
|
||||
)
|
||||
else:
|
||||
completed_issues_distribution = (
|
||||
Issue.issue_objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_cycle__cycle_id=cycle_id,
|
||||
)
|
||||
.annotate(date=TruncDate("completed_at"))
|
||||
.values("date")
|
||||
.annotate(total_completed=Count("id"))
|
||||
.values("date", "total_completed")
|
||||
.order_by("date")
|
||||
)
|
||||
.annotate(date=TruncDate("completed_at"))
|
||||
.values("date")
|
||||
.annotate(total_completed=Count("id"))
|
||||
.values("date", "total_completed")
|
||||
.order_by("date")
|
||||
)
|
||||
|
||||
if module_id:
|
||||
# Get all dates between the two dates
|
||||
|
|
@ -152,31 +200,59 @@ def burndown_plot(queryset, slug, project_id, cycle_id=None, module_id=None):
|
|||
|
||||
chart_data = {str(date): 0 for date in date_range}
|
||||
|
||||
completed_issues_distribution = (
|
||||
Issue.issue_objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_module__module_id=module_id,
|
||||
if plot_type == "points":
|
||||
completed_issues_estimate_point_distribution = (
|
||||
Issue.issue_objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_module__module_id=module_id,
|
||||
estimate_point__isnull=False,
|
||||
)
|
||||
.annotate(date=TruncDate("completed_at"))
|
||||
.values("date")
|
||||
.values("date", "estimate_point__value")
|
||||
.order_by("date")
|
||||
)
|
||||
else:
|
||||
completed_issues_distribution = (
|
||||
Issue.issue_objects.filter(
|
||||
workspace__slug=slug,
|
||||
project_id=project_id,
|
||||
issue_module__module_id=module_id,
|
||||
)
|
||||
.annotate(date=TruncDate("completed_at"))
|
||||
.values("date")
|
||||
.annotate(total_completed=Count("id"))
|
||||
.values("date", "total_completed")
|
||||
.order_by("date")
|
||||
)
|
||||
.annotate(date=TruncDate("completed_at"))
|
||||
.values("date")
|
||||
.annotate(total_completed=Count("id"))
|
||||
.values("date", "total_completed")
|
||||
.order_by("date")
|
||||
)
|
||||
|
||||
for date in date_range:
|
||||
cumulative_pending_issues = total_issues
|
||||
total_completed = 0
|
||||
total_completed = sum(
|
||||
item["total_completed"]
|
||||
for item in completed_issues_distribution
|
||||
if item["date"] is not None and item["date"] <= date
|
||||
)
|
||||
cumulative_pending_issues -= total_completed
|
||||
if date > timezone.now().date():
|
||||
chart_data[str(date)] = None
|
||||
if plot_type == "points":
|
||||
cumulative_pending_issues = total_estimate_points
|
||||
total_completed = 0
|
||||
total_completed = sum(
|
||||
int(item["estimate_point__value"])
|
||||
for item in completed_issues_estimate_point_distribution
|
||||
if item["date"] is not None and item["date"] <= date
|
||||
)
|
||||
cumulative_pending_issues -= total_completed
|
||||
if date > timezone.now().date():
|
||||
chart_data[str(date)] = None
|
||||
else:
|
||||
chart_data[str(date)] = cumulative_pending_issues
|
||||
else:
|
||||
chart_data[str(date)] = cumulative_pending_issues
|
||||
cumulative_pending_issues = total_issues
|
||||
total_completed = 0
|
||||
total_completed = sum(
|
||||
item["total_completed"]
|
||||
for item in completed_issues_distribution
|
||||
if item["date"] is not None and item["date"] <= date
|
||||
)
|
||||
cumulative_pending_issues -= total_completed
|
||||
if date > timezone.now().date():
|
||||
chart_data[str(date)] = None
|
||||
else:
|
||||
chart_data[str(date)] = cumulative_pending_issues
|
||||
|
||||
return chart_data
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue