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>
This commit is contained in:
Bavisetti Narayan 2024-06-05 17:47:48 +05:30 committed by GitHub
parent 028e70c4c1
commit 452e8f39ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 424 additions and 112 deletions

View File

@ -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"),
)

View File

@ -30,7 +30,7 @@ from .project import (
ProjectIdentifierSerializer,
ProjectLiteSerializer,
ProjectMemberLiteSerializer,
ProjectDeployBoardSerializer,
DeployBoardSerializer,
ProjectMemberAdminSerializer,
ProjectPublicMemberSerializer,
ProjectMemberRoleSerializer,

View File

@ -13,7 +13,7 @@ from plane.db.models import (
ProjectMember,
ProjectMemberInvite,
ProjectIdentifier,
ProjectDeployBoard,
DeployBoard,
ProjectPublicMember,
)
@ -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",

View File

@ -12,7 +12,7 @@ from plane.app.views import (
ProjectFavoritesViewSet,
UserProjectInvitationsViewset,
ProjectPublicCoverImagesEndpoint,
ProjectDeployBoardViewSet,
DeployBoardViewSet,
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",

View File

@ -4,7 +4,7 @@ from .project.base import (
ProjectUserViewsEndpoint,
ProjectFavoritesViewSet,
ProjectPublicCoverImagesEndpoint,
ProjectDeployBoardViewSet,
DeployBoardViewSet,
ProjectArchiveUnarchiveEndpoint,
)

View File

@ -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,
)
@ -138,7 +138,7 @@ class ProjectViewSet(BaseViewSet):
)
.annotate(
is_deployed=Exists(
ProjectDeployBoard.objects.filter(
DeployBoard.objects.filter(
project_id=OuterRef("pk"),
workspace__slug=self.kwargs.get("slug"),
)
@ -639,12 +639,12 @@ 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 (
@ -673,17 +673,17 @@ class ProjectDeployBoardViewSet(BaseViewSet):
},
)
project_deploy_board, _ = ProjectDeployBoard.objects.get_or_create(
project_deploy_board, _ = DeployBoard.objects.get_or_create(
anchor=f"{slug}/{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)

View File

@ -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,

View 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),
]

View File

@ -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,7 +54,6 @@ from .page import Page, PageFavorite, PageLabel, PageLog
from .project import (
Project,
ProjectBaseModel,
ProjectDeployBoard,
ProjectFavorite,
ProjectIdentifier,
ProjectMember,

View 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",)

View File

@ -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"""

View File

@ -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>")

View File

@ -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

View File

@ -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"),
)
@ -59,7 +59,7 @@ class InboxIssuePublicViewSet(BaseViewSet):
return InboxIssue.objects.none()
def list(self, request, slug, project_id, inbox_id):
project_deploy_board = ProjectDeployBoard.objects.get(
project_deploy_board = DeployBoard.objects.get(
workspace__slug=slug, project_id=project_id
)
if project_deploy_board.inbox is None:
@ -118,7 +118,7 @@ class InboxIssuePublicViewSet(BaseViewSet):
)
def create(self, request, slug, project_id, inbox_id):
project_deploy_board = ProjectDeployBoard.objects.get(
project_deploy_board = DeployBoard.objects.get(
workspace__slug=slug, project_id=project_id
)
if project_deploy_board.inbox is None:
@ -189,7 +189,7 @@ class InboxIssuePublicViewSet(BaseViewSet):
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(
project_deploy_board = DeployBoard.objects.get(
workspace__slug=slug, project_id=project_id
)
if project_deploy_board.inbox is None:
@ -256,7 +256,7 @@ class InboxIssuePublicViewSet(BaseViewSet):
)
def retrieve(self, request, slug, project_id, inbox_id, pk):
project_deploy_board = ProjectDeployBoard.objects.get(
project_deploy_board = DeployBoard.objects.get(
workspace__slug=slug, project_id=project_id
)
if project_deploy_board.inbox is None:
@ -280,7 +280,7 @@ class InboxIssuePublicViewSet(BaseViewSet):
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(
project_deploy_board = DeployBoard.objects.get(
workspace__slug=slug, project_id=project_id
)
if project_deploy_board.inbox is None:

View File

@ -44,7 +44,7 @@ from plane.db.models import (
ProjectMember,
IssueReaction,
CommentReaction,
ProjectDeployBoard,
DeployBoard,
IssueVote,
ProjectPublicMember,
)
@ -76,7 +76,7 @@ class IssueCommentPublicViewSet(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"),
)
@ -103,11 +103,11 @@ 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(
project_deploy_board = DeployBoard.objects.get(
workspace__slug=slug, project_id=project_id
)
@ -151,7 +151,7 @@ class IssueCommentPublicViewSet(BaseViewSet):
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(
project_deploy_board = DeployBoard.objects.get(
workspace__slug=slug, project_id=project_id
)
@ -184,7 +184,7 @@ class IssueCommentPublicViewSet(BaseViewSet):
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(
project_deploy_board = DeployBoard.objects.get(
workspace__slug=slug, project_id=project_id
)
@ -221,7 +221,7 @@ 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"),
)
@ -236,11 +236,11 @@ 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(
project_deploy_board = DeployBoard.objects.get(
workspace__slug=slug, project_id=project_id
)
@ -280,7 +280,7 @@ class IssueReactionPublicViewSet(BaseViewSet):
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(
project_deploy_board = DeployBoard.objects.get(
workspace__slug=slug, project_id=project_id
)
@ -319,7 +319,7 @@ class CommentReactionPublicViewSet(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"),
)
@ -334,11 +334,11 @@ class CommentReactionPublicViewSet(BaseViewSet):
.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(
project_deploy_board = DeployBoard.objects.get(
workspace__slug=slug, project_id=project_id
)
@ -380,7 +380,7 @@ class CommentReactionPublicViewSet(BaseViewSet):
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(
project_deploy_board = DeployBoard.objects.get(
workspace__slug=slug, project_id=project_id
)
if not project_deploy_board.reactions:
@ -421,7 +421,7 @@ class IssueVotePublicViewSet(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"),
)
@ -434,7 +434,7 @@ class IssueVotePublicViewSet(BaseViewSet):
.filter(project_id=self.kwargs.get("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):
@ -513,7 +513,7 @@ class ProjectIssuesPublicEndpoint(BaseAPIView):
]
def get(self, request, slug, project_id):
if not ProjectDeployBoard.objects.filter(
if not DeployBoard.objects.filter(
workspace__slug=slug, project_id=project_id
).exists():
return Response(

View File

@ -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,
)
@ -24,10 +24,10 @@ class ProjectDeployBoardPublicSettingsEndpoint(BaseAPIView):
]
def get(self, request, slug, project_id):
project_deploy_board = ProjectDeployBoard.objects.get(
project_deploy_board = DeployBoard.objects.get(
workspace__slug=slug, project_id=project_id
)
serializer = ProjectDeployBoardSerializer(project_deploy_board)
serializer = DeployBoardSerializer(project_deploy_board)
return Response(serializer.data, status=status.HTTP_200_OK)
@ -41,7 +41,7 @@ class WorkspaceProjectDeployBoardEndpoint(BaseAPIView):
Project.objects.filter(workspace__slug=slug)
.annotate(
is_public=Exists(
ProjectDeployBoard.objects.filter(
DeployBoard.objects.filter(
workspace__slug=slug, project_id=OuterRef("pk")
)
)

View File

@ -15,7 +15,7 @@ export type TIssue = {
priority: TIssuePriorities;
label_ids: string[];
assignee_ids: string[];
estimate_point: number | null;
estimate_point: string | null;
sub_issues_count: number;
attachment_count: number;

View File

@ -24,7 +24,7 @@ import { Tooltip, BlockedIcon, BlockerIcon, RelatedIcon, LayersIcon, DiceIcon }
// helpers
import { renderFormattedDate } from "@/helpers/date-time.helper";
import { capitalizeFirstLetter } from "@/helpers/string.helper";
import { useEstimate, useLabel } from "@/hooks/store";
import { useLabel } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
// types
@ -97,22 +97,6 @@ const LabelPill = observer(({ labelId, workspaceSlug }: { labelId: string; works
);
});
const EstimatePoint = observer((props: { point: string }) => {
const { point } = props;
const { areEstimatesEnabledForCurrentProject, getEstimatePointValue } = useEstimate();
const currentPoint = Number(point) + 1;
const estimateValue = getEstimatePointValue(Number(point), null);
return (
<span className="font-medium text-custom-text-100 whitespace-nowrap">
{areEstimatesEnabledForCurrentProject
? estimateValue
: `${currentPoint} ${currentPoint > 1 ? "points" : "point"}`}
</span>
);
});
const inboxActivityMessage = {
declined: {
showIssue: "declined issue",
@ -267,7 +251,7 @@ const activityDetails: {
else
return (
<>
set the estimate point to <EstimatePoint point={activity.new_value} />
set the estimate point to {activity.new_value}
{showIssue && (
<>
{" "}

View File

@ -19,15 +19,15 @@ type Props = TDropdownProps & {
button?: ReactNode;
dropdownArrow?: boolean;
dropdownArrowClassName?: string;
onChange: (val: number | null) => void;
onChange: (val: string | null) => void;
onClose?: () => void;
projectId: string;
value: number | null;
value: string | null;
};
type DropdownOptions =
| {
value: number | null;
value: string | null;
query: string;
content: JSX.Element;
}[]
@ -80,7 +80,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
const activeEstimate = getProjectActiveEstimateDetails(projectId);
const options: DropdownOptions = sortBy(activeEstimate?.points ?? [], "key")?.map((point) => ({
value: point.key,
value: point.id,
query: `${point?.value}`,
content: (
<div className="flex items-center gap-2">
@ -120,7 +120,7 @@ export const EstimateDropdown: React.FC<Props> = observer((props) => {
setQuery,
});
const dropdownOnChange = (val: number | null) => {
const dropdownOnChange = (val: string | null) => {
onChange(val);
handleClose();
};

View File

@ -2,7 +2,7 @@ import { FC } from "react";
import { observer } from "mobx-react";
import { Triangle } from "lucide-react";
// hooks
import { useEstimate, useIssueDetail } from "@/hooks/store";
import { useIssueDetail } from "@/hooks/store";
// components
import { IssueActivityBlockComponent, IssueLink } from "./";
@ -14,15 +14,11 @@ export const IssueEstimateActivity: FC<TIssueEstimateActivity> = observer((props
const {
activity: { getActivityById },
} = useIssueDetail();
const { areEstimatesEnabledForCurrentProject, getEstimatePointValue } = useEstimate();
const activity = getActivityById(activityId);
if (!activity) return <></>;
const estimateValue = getEstimatePointValue(Number(activity.new_value), null);
const currentPoint = Number(activity.new_value) + 1;
return (
<IssueActivityBlockComponent
icon={<Triangle size={14} color="#6b7280" aria-hidden="true" />}
@ -31,15 +27,7 @@ export const IssueEstimateActivity: FC<TIssueEstimateActivity> = observer((props
>
<>
{activity.new_value ? `set the estimate point to ` : `removed the estimate point `}
{activity.new_value && (
<>
<span className="font-medium text-custom-text-100">
{areEstimatesEnabledForCurrentProject
? estimateValue
: `${currentPoint} ${currentPoint > 1 ? "points" : "point"}`}
</span>
</>
)}
{activity.new_value ? activity.new_value : activity?.old_value || ""}
{showIssue && (activity.new_value ? ` to ` : ` from `)}
{showIssue && <IssueLink activityId={activityId} />}.
</>

View File

@ -318,7 +318,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
<span>Estimate</span>
</div>
<EstimateDropdown
value={issue?.estimate_point !== null ? issue.estimate_point : null}
value={issue?.estimate_point != null ? issue.estimate_point : null}
onChange={(val) => issueOperations.update(workspaceSlug, projectId, issueId, { estimate_point: val })}
projectId={projectId}
disabled={!isEditable}

View File

@ -220,7 +220,7 @@ export const IssueProperties: React.FC<IIssueProperties> = observer((props) => {
);
};
const handleEstimate = (value: number | null) => {
const handleEstimate = (value: string | null) => {
updateIssue &&
updateIssue(issue.project_id, issue.id, { estimate_point: value }).then(() => {
captureIssueEvent({

View File

@ -495,7 +495,7 @@ export const handleGroupDragDrop = async (
// update updatedIssue values based on the source and destination groupIds
if (source.groupId && destination.groupId && source.groupId !== destination.groupId) {
const groupKey = ISSUE_FILTER_DEFAULT_DATA[groupBy];
let groupValue = clone(sourceIssue[groupKey]);
let groupValue: any = clone(sourceIssue[groupKey]);
// If groupValues is an array, remove source groupId and add destination groupId
if (Array.isArray(groupValue)) {
@ -515,7 +515,7 @@ export const handleGroupDragDrop = async (
// update updatedIssue values based on the source and destination subGroupIds
if (subGroupBy && source.subGroupId && destination.subGroupId && source.subGroupId !== destination.subGroupId) {
const subGroupKey = ISSUE_FILTER_DEFAULT_DATA[subGroupBy];
let subGroupValue = clone(sourceIssue[subGroupKey]);
let subGroupValue: any = clone(sourceIssue[subGroupKey]);
// If subGroupValue is an array, remove source subGroupId and add destination subGroupId
if (Array.isArray(subGroupValue)) {

View File

@ -19,7 +19,7 @@ export interface IEstimateStore {
activeEstimateDetails: IEstimate | null;
// computed actions
areEstimatesEnabledForProject: (projectId: string) => boolean;
getEstimatePointValue: (estimateKey: number | null, projectId: string | null) => string;
getEstimatePointValue: (estimateKey: string | null, projectId: string | null) => string;
getProjectEstimateById: (estimateId: string) => IEstimate | null;
getProjectActiveEstimateDetails: (projectId: string) => IEstimate | null;
// fetch actions
@ -110,10 +110,10 @@ export class EstimateStore implements IEstimateStore {
/**
* @description returns the point value for the given estimate key to display in the UI
*/
getEstimatePointValue = computedFn((estimateKey: number | null, projectId: string | null) => {
getEstimatePointValue = computedFn((estimateKey: string | null, projectId: string | null) => {
if (estimateKey === null) return "None";
const activeEstimate = projectId ? this.getProjectActiveEstimateDetails(projectId) : this.activeEstimateDetails;
return activeEstimate?.points?.find((point) => point.key === estimateKey)?.value || "None";
return activeEstimate?.points?.find((point) => point.id === estimateKey)?.value || "None";
});
/**

View File

@ -4,10 +4,10 @@ import sortBy from "lodash/sortBy";
import uniq from "lodash/uniq";
import update from "lodash/update";
import { action, makeObservable, observable, runInAction } from "mobx";
import { TIssueActivityComment, TIssueActivity, TIssueActivityMap, TIssueActivityIdMap } from "@plane/types";
// services
import { IssueActivityService } from "@/services/issue";
// types
import { TIssueActivityComment, TIssueActivity, TIssueActivityMap, TIssueActivityIdMap } from "@plane/types";
import { IIssueDetail } from "./root.store";
export type TActivityLoader = "fetch" | "mutate" | undefined;
@ -117,10 +117,10 @@ export class IssueActivityStore implements IIssueActivityStore {
this.loader = loaderType;
let props = {};
const _activityIds = this.getActivitiesByIssueId(issueId);
if (_activityIds && _activityIds.length > 0) {
const _activity = this.getActivityById(_activityIds[_activityIds.length - 1]);
if (_activity) props = { created_at__gt: _activity.created_at };
const currentActivityIds = this.getActivitiesByIssueId(issueId);
if (currentActivityIds && currentActivityIds.length > 0) {
const currentActivity = this.getActivityById(currentActivityIds[currentActivityIds.length - 1]);
if (currentActivity) props = { created_at__gt: currentActivity.created_at };
}
const activities = await this.issueActivityService.getIssueActivities(workspaceSlug, projectId, issueId, props);
@ -128,9 +128,9 @@ export class IssueActivityStore implements IIssueActivityStore {
const activityIds = activities.map((activity) => activity.id);
runInAction(() => {
update(this.activities, issueId, (_activityIds) => {
if (!_activityIds) return activityIds;
return uniq(concat(_activityIds, activityIds));
update(this.activities, issueId, (currentActivityIds) => {
if (!currentActivityIds) return activityIds;
return uniq(concat(currentActivityIds, activityIds));
});
activities.forEach((activity) => {
set(this.activityMap, activity.id, activity);