diff --git a/apiserver/plane/api/views/project.py b/apiserver/plane/api/views/project.py index 019ab704e..408e14fed 100644 --- a/apiserver/plane/api/views/project.py +++ b/apiserver/plane/api/views/project.py @@ -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"), ) diff --git a/apiserver/plane/app/serializers/__init__.py b/apiserver/plane/app/serializers/__init__.py index bdcdf6c0d..d8364f931 100644 --- a/apiserver/plane/app/serializers/__init__.py +++ b/apiserver/plane/app/serializers/__init__.py @@ -30,7 +30,7 @@ from .project import ( ProjectIdentifierSerializer, ProjectLiteSerializer, ProjectMemberLiteSerializer, - ProjectDeployBoardSerializer, + DeployBoardSerializer, ProjectMemberAdminSerializer, ProjectPublicMemberSerializer, ProjectMemberRoleSerializer, diff --git a/apiserver/plane/app/serializers/project.py b/apiserver/plane/app/serializers/project.py index 96d92f340..d9ea99f1e 100644 --- a/apiserver/plane/app/serializers/project.py +++ b/apiserver/plane/app/serializers/project.py @@ -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", diff --git a/apiserver/plane/app/urls/project.py b/apiserver/plane/app/urls/project.py index 7ea636df8..d9c6f0f81 100644 --- a/apiserver/plane/app/urls/project.py +++ b/apiserver/plane/app/urls/project.py @@ -12,7 +12,7 @@ from plane.app.views import ( ProjectFavoritesViewSet, UserProjectInvitationsViewset, ProjectPublicCoverImagesEndpoint, - ProjectDeployBoardViewSet, + DeployBoardViewSet, UserProjectRolesEndpoint, ProjectArchiveUnarchiveEndpoint, ) @@ -157,7 +157,7 @@ urlpatterns = [ ), path( "workspaces//projects//project-deploy-boards/", - ProjectDeployBoardViewSet.as_view( + DeployBoardViewSet.as_view( { "get": "list", "post": "create", @@ -167,7 +167,7 @@ urlpatterns = [ ), path( "workspaces//projects//project-deploy-boards//", - ProjectDeployBoardViewSet.as_view( + DeployBoardViewSet.as_view( { "get": "retrieve", "patch": "partial_update", diff --git a/apiserver/plane/app/views/__init__.py b/apiserver/plane/app/views/__init__.py index 0c489593d..592d897e0 100644 --- a/apiserver/plane/app/views/__init__.py +++ b/apiserver/plane/app/views/__init__.py @@ -4,7 +4,7 @@ from .project.base import ( ProjectUserViewsEndpoint, ProjectFavoritesViewSet, ProjectPublicCoverImagesEndpoint, - ProjectDeployBoardViewSet, + DeployBoardViewSet, ProjectArchiveUnarchiveEndpoint, ) diff --git a/apiserver/plane/app/views/project/base.py b/apiserver/plane/app/views/project/base.py index 39db11871..a62d6d6dd 100644 --- a/apiserver/plane/app/views/project/base.py +++ b/apiserver/plane/app/views/project/base.py @@ -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) diff --git a/apiserver/plane/bgtasks/issue_activites_task.py b/apiserver/plane/bgtasks/issue_activites_task.py index 007b3e48c..67cda14af 100644 --- a/apiserver/plane/bgtasks/issue_activites_task.py +++ b/apiserver/plane/bgtasks/issue_activites_task.py @@ -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, diff --git a/apiserver/plane/db/migrations/0067_issue_estimate.py b/apiserver/plane/db/migrations/0067_issue_estimate.py new file mode 100644 index 000000000..b341f9864 --- /dev/null +++ b/apiserver/plane/db/migrations/0067_issue_estimate.py @@ -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), + ] diff --git a/apiserver/plane/db/models/__init__.py b/apiserver/plane/db/models/__init__.py index b11ce7aa3..36718d515 100644 --- a/apiserver/plane/db/models/__init__.py +++ b/apiserver/plane/db/models/__init__.py @@ -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, diff --git a/apiserver/plane/db/models/deploy_board.py b/apiserver/plane/db/models/deploy_board.py new file mode 100644 index 000000000..41ffbc7c1 --- /dev/null +++ b/apiserver/plane/db/models/deploy_board.py @@ -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",) diff --git a/apiserver/plane/db/models/estimate.py b/apiserver/plane/db/models/estimate.py index 6ff1186c3..0713d774f 100644 --- a/apiserver/plane/db/models/estimate.py +++ b/apiserver/plane/db/models/estimate.py @@ -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""" diff --git a/apiserver/plane/db/models/issue.py b/apiserver/plane/db/models/issue.py index 527597ddc..2b07bd77b 100644 --- a/apiserver/plane/db/models/issue.py +++ b/apiserver/plane/db/models/issue.py @@ -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="

") diff --git a/apiserver/plane/db/models/project.py b/apiserver/plane/db/models/project.py index 49fca1323..ba8dbf580 100644 --- a/apiserver/plane/db/models/project.py +++ b/apiserver/plane/db/models/project.py @@ -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 diff --git a/apiserver/plane/space/views/inbox.py b/apiserver/plane/space/views/inbox.py index 9f681c160..d15e7aa39 100644 --- a/apiserver/plane/space/views/inbox.py +++ b/apiserver/plane/space/views/inbox.py @@ -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: diff --git a/apiserver/plane/space/views/issue.py b/apiserver/plane/space/views/issue.py index 8c4d6e150..7ffdf0911 100644 --- a/apiserver/plane/space/views/issue.py +++ b/apiserver/plane/space/views/issue.py @@ -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( diff --git a/apiserver/plane/space/views/project.py b/apiserver/plane/space/views/project.py index 10a3c3879..2cace08da 100644 --- a/apiserver/plane/space/views/project.py +++ b/apiserver/plane/space/views/project.py @@ -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") ) ) diff --git a/packages/types/src/issues/issue.d.ts b/packages/types/src/issues/issue.d.ts index 42c95dc4e..990b308e7 100644 --- a/packages/types/src/issues/issue.d.ts +++ b/packages/types/src/issues/issue.d.ts @@ -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; diff --git a/web/components/core/activity.tsx b/web/components/core/activity.tsx index 28d84ffe4..5def2d7a9 100644 --- a/web/components/core/activity.tsx +++ b/web/components/core/activity.tsx @@ -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 ( - - {areEstimatesEnabledForCurrentProject - ? estimateValue - : `${currentPoint} ${currentPoint > 1 ? "points" : "point"}`} - - ); -}); - const inboxActivityMessage = { declined: { showIssue: "declined issue", @@ -267,7 +251,7 @@ const activityDetails: { else return ( <> - set the estimate point to + set the estimate point to {activity.new_value} {showIssue && ( <> {" "} diff --git a/web/components/dropdowns/estimate.tsx b/web/components/dropdowns/estimate.tsx index 58243cc22..ce646f893 100644 --- a/web/components/dropdowns/estimate.tsx +++ b/web/components/dropdowns/estimate.tsx @@ -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 = 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: (
@@ -120,7 +120,7 @@ export const EstimateDropdown: React.FC = observer((props) => { setQuery, }); - const dropdownOnChange = (val: number | null) => { + const dropdownOnChange = (val: string | null) => { onChange(val); handleClose(); }; diff --git a/web/components/issues/issue-detail/issue-activity/activity/actions/estimate.tsx b/web/components/issues/issue-detail/issue-activity/activity/actions/estimate.tsx index 9179bfa38..ef6736546 100644 --- a/web/components/issues/issue-detail/issue-activity/activity/actions/estimate.tsx +++ b/web/components/issues/issue-detail/issue-activity/activity/actions/estimate.tsx @@ -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 = 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 (
issueOperations.update(workspaceSlug, projectId, issueId, { estimate_point: val })} projectId={projectId} disabled={!isEditable} diff --git a/web/components/issues/issue-layouts/properties/all-properties.tsx b/web/components/issues/issue-layouts/properties/all-properties.tsx index 22f14833e..3db467a16 100644 --- a/web/components/issues/issue-layouts/properties/all-properties.tsx +++ b/web/components/issues/issue-layouts/properties/all-properties.tsx @@ -220,7 +220,7 @@ export const IssueProperties: React.FC = observer((props) => { ); }; - const handleEstimate = (value: number | null) => { + const handleEstimate = (value: string | null) => { updateIssue && updateIssue(issue.project_id, issue.id, { estimate_point: value }).then(() => { captureIssueEvent({ diff --git a/web/components/issues/issue-layouts/utils.tsx b/web/components/issues/issue-layouts/utils.tsx index 78048b4b4..9d18426b4 100644 --- a/web/components/issues/issue-layouts/utils.tsx +++ b/web/components/issues/issue-layouts/utils.tsx @@ -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)) { diff --git a/web/store/estimate.store.ts b/web/store/estimate.store.ts index 0bb35a23b..df31cd53c 100644 --- a/web/store/estimate.store.ts +++ b/web/store/estimate.store.ts @@ -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"; }); /** diff --git a/web/store/issue/issue-details/activity.store.ts b/web/store/issue/issue-details/activity.store.ts index 562f8ef5b..dd4fe10aa 100644 --- a/web/store/issue/issue-details/activity.store.ts +++ b/web/store/issue/issue-details/activity.store.ts @@ -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);